/*****************************************************************************
* Copyright (c) 1990-2000 Digi International Inc., All Rights Reserved
* 
* This software contains proprietary and confidential information of Digi
* International Inc.  By accepting transfer of this copy, Recipient agrees
* to retain this software in confidence, to prevent disclosure to others,
* and to make no use of this software other than that for which it was
* delivered.  This is an unpublished copyrighted work of Digi International
* Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
* prohibited.
* 
* Restricted Rights Legend
*
* Use, duplication, or disclosure by the Government is subject to
* restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
* Technical Data and Computer Software clause at DFARS 252.227-7031 or
* subparagraphs (c)(1) and (2) of the Commercial Computer Software -
* Restricted Rights at 48 CFR 52.227-19, as applicable.
*
* Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
*
*****************************************************************************/


// DigiInf.cpp : Defines the initialization routines for the DLL.
//

#include "stdafx.h"
#include "DigiInf.h"
#include "ObjectFactory.h"
#include "Update.h"
#include "AddModem.h"
#include "ConfigDigiPamAPort.h"

#ifdef NT5
#include <BaseTsd.h>
#include <setupapi.h>
#include <mmc.h>
#include "DriverServices.h"
#endif

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

#if defined NT5

// The wizard page that is installed in the Found New Hardware Wizard
#include	"HwWizProtocolPropPage.h"
#include	"HwWizDigiWizPage.h"

/////////////////////////////////////////////////////////////////////////////
// CDevPrivateData implementation, used by app to track multiple instances

CDevPrivateData::CDevPrivateData(CString AdapterType, CString AdapterRegPath)
{
   m_strAdapterType = AdapterType;
   m_strAdapterRegPath = AdapterRegPath;
   m_pTree = NULL;
   m_pHwWizProtocolPropPage = NULL;
   m_pHwWizDigiWizPage = NULL;
}

CDevPrivateData::~CDevPrivateData()
{
   // We're going away, get rid of everything...
   if (NULL != m_pTree)
      delete m_pTree;
   if (NULL != m_pHwWizProtocolPropPage)
      delete m_pHwWizProtocolPropPage;
   if (NULL != m_pHwWizDigiWizPage)
      delete m_pHwWizDigiWizPage;
}

// Statics, used by the app...

// Open and return a handle to our device instance key, the caller is responsible for closing
BOOL OpenAdapterKey(HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData, HKEY * const phKey)
{
   BOOL Success = FALSE;
	// get the DRIVER property. This is stored under HKLM\CCS\ENUM\ROOT\ 
	// its of the form ClassGuid\xxxx where xxxx is the instance # under
	// HKLM\System\CCS\Control\Class\ClassGuid 
	TCHAR szInstance[MAX_PATH];
	DWORD dwReqSize=0;
	if (SetupDiGetDeviceRegistryProperty(DeviceInfoSet, DeviceInfoData, SPDRP_DRIVER,&dwReqSize, (PBYTE)&szInstance,sizeof(szInstance),NULL))
   {
		// extract the instance number
		CString strTemp(DXB_REGCLASS_VALUE);
		strTemp += szInstance;
		TRACE("instance = %s\n",strTemp);
      HKEY hKey;
      if(RegOpenKey(HKEY_LOCAL_MACHINE,strTemp,&hKey))
      {
         *phKey = hKey;
         Success = TRUE;
      }
   }
   return Success;
}

// Open and return a handle to our device's parameters key, the caller is responsible for closing
BOOL OpenAdapterParamsKey(HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData, HKEY * const phKey)
{
   BOOL Success = FALSE;
   HKEY hKey = NULL;
   HKEY hParamsKey;
   if (OpenAdapterKey(DeviceInfoSet, DeviceInfoData, &hKey))
   {
      if (RegOpenKey(hKey,DXB_REGKEY_PARAMETERS,&hParamsKey))
      {
         *phKey = hParamsKey;
         Success = TRUE;
      }
      RegCloseKey(hKey);
   }
   return Success;
}

#endif // #if defined NT5

//
//	Note!
//
//		If this DLL is dynamically linked against the MFC
//		DLLs, any functions exported from this DLL which
//		call into MFC must have the AFX_MANAGE_STATE macro
//		added at the very beginning of the function.
//
//		For example:
//
//		extern "C" BOOL PASCAL EXPORT ExportedFunction()
//		{
//			AFX_MANAGE_STATE(AfxGetStaticModuleState());
//			// normal function body here
//		}
//
//		It is very important that this macro appear in each
//		function, prior to any calls into MFC.  This means that
//		it must appear as the first statement within the 
//		function, even before any object variable declarations
//		as their constructors may generate calls into the MFC
//		DLL.
//
//		Please see MFC Technical Notes 33 and 58 for additional
//		details.
//


/////////////////////////////////////////////////////////////////////////////
// CINFEntryApp

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

/////////////////////////////////////////////////////////////////////////////
// CINFEntryApp construction

CINFEntryApp::CINFEntryApp()
{
   try
   {
      AttachToFrameWork();
      m_fInWizard = FALSE;

#if defined NT5
      m_fInTreeDialog = TRUE;    // we are always in dialog mode, because of the property page
      m_pCriticalSection = new CCriticalSection; // Synchro for the private data list
#else
      m_pAdapter = NULL;
      m_pTreeDialog = NULL;
      m_fInTreeDialog = FALSE;
#endif

      SetHelpFilePath();
   }
   catch (CAffirmException ae)
   {
   }
}

CINFEntryApp::~CINFEntryApp()
{
   try
   {
      TRACE("CINFEntryApp::~CINFEntryApp()\n");

#if defined NT5
      // Delete anything that might be on the private data list...
   	m_pCriticalSection->Lock();
      CDevPrivateData * pPrivateData = NULL;
      while (!m_PrivateDataList.IsEmpty())
      {
         pPrivateData = (CDevPrivateData *)m_PrivateDataList.RemoveHead();
         if (NULL != pPrivateData)
            delete pPrivateData;
      }
   	m_pCriticalSection->Unlock();
     	if (NULL != m_pCriticalSection)
   		delete m_pCriticalSection;

#else // #if defined NT5
      if (m_pAdapter != NULL)
         DeleteConfigObject((CConfigObject **)&m_pAdapter);
      if (m_pTreeDialog != NULL)
         delete m_pTreeDialog;
#endif // #if defined NT5

		if (m_InfCwnd.GetSafeHwnd() != NULL)
			m_InfCwnd.Detach();
   }
   catch (CAffirmException ae)
   {
   }
}

void CINFEntryApp::SetHelpFilePath(void)
{
#ifndef NT5
try
   {
      // Work with a CString here since it's a little easier
      CString strNewHelpFilePath = m_pszHelpFilePath;

      // Find the last back slash, and truncate the path there
      int iLastSlash = strNewHelpFilePath.ReverseFind(TEXT('\\'));
      strNewHelpFilePath = (iLastSlash >= 0) ? strNewHelpFilePath.Left(iLastSlash + 1) : TEXT("");

      // Now append new help file name
      strNewHelpFilePath += TEXT("DigiRAS.hlp");

      // This reassignment is described in MSDN, see CWinApp::m_pszHelpFilePath
      TRACE("Reassigning help file path %s\n", strNewHelpFilePath);
      free((void *)m_pszHelpFilePath);
      m_pszHelpFilePath = _tcsdup(strNewHelpFilePath);
      AffirmThrow(m_pszHelpFilePath != NULL);
   }
   catch (CAffirmException ae)
   {
   }
#endif
}

// For NT5, these operations are handled by the coinstaller on a per instance basis
// NT4 handles these globally in the app
#if !defined NT5
UINT CINFEntryApp::RunInstallWizard(CAdapter * const pAdapter, BOOL fSetParameters /* = FALSE */)
{
   UINT nResponse = IDABORT;

   try
   {
      AffirmThrow(pAdapter != NULL);
      AffirmThrow(!m_fInWizard);

      TRACE("Running install wizard\n");

      CConfigWizSheet ws(TEXT(""), (m_fInTreeDialog) ? m_pTreeDialog : &m_InfCwnd);

      AffirmThrow(pAdapter->AddWizardPages(&ws));

      CWnd * pOldActiveWnd = m_pMainWnd;
      m_pMainWnd = &ws;

      m_fInWizard = TRUE;
      switch (ws.DoModal())
      {
         case ID_WIZFINISH:
         {
            TRACE("The wizard finished properly\n");
            if (fSetParameters) AffirmThrow(pAdapter->SetParameters());
            nResponse = IDOK;
            break;
         }

         case IDCANCEL:
         {
            TRACE("The user canceled out of the wizard!\n");
            nResponse = IDCANCEL;
            break;
         }

         default: TRACE("Install wizard failed!\n"); break;
      }
      m_fInWizard = FALSE;

      m_pMainWnd = pOldActiveWnd;
   }
   catch (CAffirmException ae)
   {
   }

   return nResponse;
}

UINT CINFEntryApp::RunTreeDialog(void)
{
   UINT nResponse = IDABORT;

   try
   {
      AffirmThrow(m_pAdapter != NULL);
      AffirmThrow(!m_fInTreeDialog);

      TRACE("Running tree dialog\n");

      if (m_pTreeDialog == NULL)
#ifdef NT5
			m_pTreeDialog = new CTreeDialog();
#else
			m_pTreeDialog = new CTreeDialog(&m_InfCwnd);
#endif
      AffirmThrow(m_pTreeDialog != NULL);

      m_pTreeDialog->SetAppObject(this);

      CWnd * pOldActiveWnd = m_pMainWnd;
      m_pMainWnd = m_pTreeDialog;
      m_fInTreeDialog = TRUE;
      nResponse = m_pTreeDialog->DoModal();
      m_fInTreeDialog = FALSE;
      m_pMainWnd = pOldActiveWnd;

      TRACE("Tree dialog returned: %s\n", (nResponse == IDOK) ? TEXT("IDOK") : TEXT("IDCANCEL"));
   }
   catch (CAffirmException ae)
   {
   }

   return nResponse;
}

BOOL CINFEntryApp::RefreshAdapter(CAdapter ** const ppAdapter /* = NULL */)
{
   BOOL fSuccess = FALSE;

   try
   {
      TRACE("Refreshing adapter\n");
      CAdapter ** ppAdapterToRefresh = (ppAdapter != NULL) ? ppAdapter : &m_pAdapter;
      AffirmThrow(ppAdapterToRefresh != NULL);

      if (*ppAdapterToRefresh != NULL) DeleteConfigObject((CConfigObject **)ppAdapterToRefresh);

      AffirmThrow(NewConfigObject((CConfigObject **)ppAdapterToRefresh, CConfigObject::ADAPTER, m_strAdapterType, HKEY_LOCAL_MACHINE, m_strAdapterRegPath, NULL));
      AffirmThrow(*ppAdapterToRefresh != NULL);

      fSuccess = TRUE;
   }
   catch (CAffirmException ae)
   {
   }

   return fSuccess;
}

UINT CINFEntryApp::GetAGoin(void)
{
   UINT nResponse = IDABORT;

   try
   {
      RefreshAdapter();

      HKEY hAdapter = NULL;
      AffirmThrow(RegOpenKey(HKEY_LOCAL_MACHINE, m_strAdapterRegPath, &hAdapter));
      AffirmThrow(hAdapter != NULL);

      try
      {
         BOOL fHasWizard = m_pAdapter->AdapterHasWizard();

         static CString const strFirstConfig = TEXT("FirstConfiguration");
         BOOL fFirstConfig = TRUE;
         AffirmThrow(RegReadBoolValue(hAdapter, strFirstConfig, &fFirstConfig, TRUE));

         if (fFirstConfig)
         {
            if (fHasWizard)
				{
               nResponse = RunInstallWizard(m_pAdapter, TRUE);
				}
				else
				{
					m_pAdapter->SetParameters();
					nResponse = IDOK;
				}

            if (nResponse == IDOK)
                  AffirmThrow(RegWriteBoolValue(hAdapter, strFirstConfig, FALSE));
         }
         else
         {
            BOOL fHWChanged = FALSE;
            BOOL fRunWizard = FALSE;
            AffirmThrow(m_pAdapter->DetectHWChange(fHWChanged, fRunWizard));
            if (fHasWizard && fRunWizard)
               RunInstallWizard(m_pAdapter, TRUE);
            nResponse = RunTreeDialog();
         }
      }
      catch (CAffirmException ae)
      {
      }

      RegCloseKey(hAdapter);
   }
   catch (CAffirmException ae)
   {
   }

   return nResponse;
}
#endif // #if !defined NT5

// NT5 specific handlers, necessary to handle multiple instances of the config
#if defined NT5
CDevPrivateData * const CINFEntryApp::GetDevicePrivateData(HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData)
{
   CString strAdapterType;
   CString strAdapterRegPath;
   HKEY hParamsKey;

   BOOL fFound = FALSE;
   CDevPrivateData * pPrivateData = NULL;

   try
   {
      AffirmThrow(OpenAdapterParamsKey(DeviceInfoSet, DeviceInfoData, &hParamsKey));
      AffirmThrow(RegReadStrValue(hParamsKey,DXB_REGVAL_OPTION,strAdapterType));
      AffirmThrow(RegReadStrValue(hParamsKey,DXB_REGVAL_REGPATH,strAdapterRegPath));
      RegCloseKey(hParamsKey);

      TRACE("CINFEntryApp::GetDevicePrivateData(): Enter\n");
      TRACE("    adapter = %s\n",strAdapterType);
      TRACE("    regpath = %s\n",strAdapterRegPath);

      // Search the list for this device instance in case it's already been allocated
      // If so, just return the existing ptr. If not, create a new entry
   	m_pCriticalSection->Lock();
      {
         POSITION pos = m_PrivateDataList.GetHeadPosition();
         while (NULL != pos)
         {
            pPrivateData = (CDevPrivateData *)m_PrivateDataList.GetAt(pos);
            // Compare against the device instance info
            if ( (strAdapterType == pPrivateData->m_strAdapterType) &&
                 (strAdapterRegPath == pPrivateData->m_strAdapterRegPath)
              )
            {
               fFound = TRUE;
               TRACE("CINFEntryApp::GetDevicePrivateData(): Returning existing private data\n");
               break;
            }
            (void)m_PrivateDataList.GetNext(pos); // We only want to advance the position
         }

         if (!fFound)
         {
            pPrivateData = (CDevPrivateData *)(new CDevPrivateData(strAdapterType, strAdapterRegPath));
            TRACE("CINFEntryApp::GetDevicePrivateData(): Returning new private data\n");
            m_PrivateDataList.AddHead(pPrivateData);
         }
      }
      m_pCriticalSection->Unlock();
   }
   catch (CAffirmException ae)
   {
   }

   return pPrivateData;
}

void CINFEntryApp::RemoveDevicePrivateData(HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData)
{
   CString strAdapterType;
   CString strAdapterRegPath;
   HKEY hParamsKey;

   BOOL fGotInstance = FALSE;
   BOOL fFoundData = FALSE;
   CDevPrivateData * pPrivateData = NULL;

   // Is the adapter instance still around?
   if (OpenAdapterParamsKey(DeviceInfoSet, DeviceInfoData, &hParamsKey))
   {
      fGotInstance = (RegReadStrValue(hParamsKey,DXB_REGVAL_OPTION,strAdapterType)) &&
                     (RegReadStrValue(hParamsKey,DXB_REGVAL_REGPATH,strAdapterRegPath));
      RegCloseKey(hParamsKey);
   }

   // Check for any private data associated with the instance
   if (fGotInstance)
   {
   	m_pCriticalSection->Lock();
      {
         POSITION pos = m_PrivateDataList.GetHeadPosition();
         while (NULL != pos)
         {
            pPrivateData = (CDevPrivateData *)m_PrivateDataList.GetAt(pos);
            // Compare against the device instance info
            if ( (strAdapterType == pPrivateData->m_strAdapterType) &&
                 (strAdapterRegPath == pPrivateData->m_strAdapterRegPath)
              )
            {
               m_PrivateDataList.RemoveAt(pos);
               delete pPrivateData;
               TRACE("CINFEntryApp::GetDevicePrivateData(): Deleted private data\n");
               break;
            }
            (void)m_PrivateDataList.GetNext(pos);
         }
      }
   	m_pCriticalSection->Unlock();
   }
}

void CINFEntryApp::RemovePamA(void)
{
	SC_HANDLE SCMHandle = NULL;
	SC_HANDLE ServiceHandle = NULL;

	try
	{
		SCMHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
		AffirmThrow(SCMHandle != NULL);

		ServiceHandle = OpenService(SCMHandle, DXB_DIGIPAMA_SERVICE_NAME, SERVICE_ALL_ACCESS);
      if (NULL != ServiceHandle)
      {
   		SERVICE_STATUS Status = {0};
	      if (ControlService(ServiceHandle, SERVICE_CONTROL_STOP, &Status))
	      {
		      // The ControlService() returned TRUE, so the service must have been
		      // running, and the stop if pending.
		      TRACE("Waiting for service to stop.");
		      for (int Try = 0;
				      Try < 30 &&
				      QueryServiceStatus(ServiceHandle, &Status) &&
				      Status.dwCurrentState != SERVICE_STOPPED;
				      Try++)
		      {
			      TRACE(".");
			      Sleep(1000);
		      }
		      TRACE("\n");
	      }

	      TRACE("%s has been succeedfully stopped\n", DXB_DIGIPAMA_SERVICE_NAME);

         DeleteService(ServiceHandle);
      }
   }
   catch(CAffirmException ae)
   {
   }

	if (ServiceHandle != NULL)
		CloseServiceHandle(ServiceHandle);

	if (SCMHandle != NULL)
		CloseServiceHandle(SCMHandle);
}

#endif // #if defined NT5

void CINFEntryApp::WinHelp(DWORD dwData, UINT nCmd) 
{
   BOOL fCallDfltWinHelp = TRUE;

   TRACE("CINFEntryApp::WinHelp(%d, %d)\n", dwData, nCmd);

   if (nCmd == HELP_QUIT)
      fCallDfltWinHelp = !(m_fInTreeDialog && m_fInWizard);

#ifdef NT5

   if (fCallDfltWinHelp) HtmlHelp(GetActiveWindow(), DIGIHELPFILE, HH_HELP_CONTEXT, dwData);

#else
   
   if (fCallDfltWinHelp) CWinApp::WinHelp(dwData, nCmd);

#endif


}

void CINFEntryApp::SetConfigureCwnd(char const * const pszInfWnd)
{
   try
   {
		AffirmThrow(pszInfWnd != NULL);

		TRACE("Setting the INF CWnd member variable based on the INF hWnd (%s)\n", pszInfWnd);

		if (m_InfCwnd.GetSafeHwnd() != NULL)
		{
			TRACE("Already attached to some window (%s), ... detaching\n");
			m_InfCwnd.Detach();
		}

		HWND hwnd = (HWND) strtol(pszInfWnd, NULL, 16);
		if (::IsWindow(hwnd))
			AffirmThrow(m_InfCwnd.Attach(hwnd));
   }
   catch (CAffirmException ae)
   {
   }

   return;
}

void CINFEntryApp::ConfigPamA(void)
{
	CConfigDigiPamAPort PamACfgDialog(&m_InfCwnd);
	PamACfgDialog.DoModal();
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CINFEntryApp object

CINFEntryApp theApp;

/////////////////////////////////////////////////////////////////////////////
// The entry points into this DLL used by the INF under NT4.0


static char const * const g_pszINFTextOutError  = "Error";
static char const * const g_pszINFTextOutCancel = "Cancel";
static char const * const g_pszINFTextOutOK     = "OK";

#define INFENTRYPOINT_FAILURE 0
#define INFENTRYPOINT_SUCCESS 1

extern "C" DWORD APIENTRY Configure(DWORD const cArgs, char const * const apszArgs[], char const ** const pszTextOut)
{
   AFX_MANAGE_STATE(AfxGetStaticModuleState());

   TRACE("Entering Configure()\n");

#if defined(_DEBUG)
   TRACE("Configure arguments:\n");
   for (DWORD i = 0; i < cArgs; i++) TRACE("  apszArgs[%d] == %s\n", i, apszArgs[i]);
#endif

   DWORD dwInfEntryPointStatus = INFENTRYPOINT_FAILURE;
   *pszTextOut = g_pszINFTextOutError;

   // For NT5, this is unused, stubbed out
#if !defined NT5

   try
   {
      try
      {
         enum
         {
            ARG_WINDOWHANDLE,
            ARG_ADAPTERTYPE,
            ARG_ADAPTERINSTANCE,
            ARG_COUNT,
         };

         AffirmThrow(cArgs == ARG_COUNT);

         TRACE("Configure(): Configuring %s (%s)\n", apszArgs[ARG_ADAPTERTYPE], apszArgs[ARG_ADAPTERINSTANCE]);

			theApp.SetConfigureCwnd(apszArgs[ARG_WINDOWHANDLE]);

			if (stricmp(apszArgs[ARG_ADAPTERTYPE], "PAMA") == 0)
			{
				theApp.ConfigPamA();
				*pszTextOut = g_pszINFTextOutOK;
				dwInfEntryPointStatus = INFENTRYPOINT_SUCCESS;
			}
			else
			{
				theApp.SetAdapterType(apszArgs[ARG_ADAPTERTYPE]);
				theApp.SetAdapterInstance(apszArgs[ARG_ADAPTERINSTANCE]);
				theApp.SetAdapterRegPath(CString(TEXT("SYSTEM\\CurrentControlSet\\Services\\")) + apszArgs[ARG_ADAPTERINSTANCE] + CString(TEXT("\\Parameters")));

				switch (theApp.GetAGoin())
				{
					case IDOK:
					{
						*pszTextOut = g_pszINFTextOutOK;
						dwInfEntryPointStatus = INFENTRYPOINT_SUCCESS;
						break;
					}

					case IDCANCEL:
					{
						*pszTextOut = g_pszINFTextOutCancel;
						dwInfEntryPointStatus = INFENTRYPOINT_SUCCESS;
						break;
					}

					default: TRACE("GetAGoin() failed\n!"); break;
				}
			}
      }
      catch (CAffirmException ae)
      {
         throw CConfigException();
      }
   }
   catch (CConfigException ce)
   {
      ce.ReportError();
   }

#endif // #if !defined NT5

   TRACE("Exiting Configure(): dwInfEntryPointStatus == %d, *pszTextOut == %s\n", dwInfEntryPointStatus, *pszTextOut);
   return dwInfEntryPointStatus;
}

// NOTE: This one is left in only for backwards compatibility
extern "C" DWORD APIENTRY INFEntryPoint(DWORD const cArgs, char const * const apszArgs[], char const ** const pszTextOut)
{
   TRACE("Obsolete INFEntyrPoint() called\n");
   return Configure(cArgs, apszArgs, pszTextOut);
}

extern "C" DWORD APIENTRY Remove(DWORD const cArgs, char const * const apszArgs[], char const ** const pszTextOut)
{
   AFX_MANAGE_STATE(AfxGetStaticModuleState());

   TRACE("Entering Remove()\n");

#if defined(_DEBUG)
   TRACE("Remove arguments:\n");
   for (DWORD i = 0; i < cArgs; i++) TRACE("  apszArgs[%d] == %s\n", i, apszArgs[i]);
#endif

   DWORD dwInfEntryPointStatus = INFENTRYPOINT_FAILURE;
   *pszTextOut = g_pszINFTextOutError;

   try
   {
      *pszTextOut = g_pszINFTextOutOK;
      dwInfEntryPointStatus = INFENTRYPOINT_SUCCESS;
   }
   catch (CConfigException ce)
   {
      ce.ReportError();
   }

   TRACE("Exiting Remove(): dwInfEntryPointStatus == %d, *pszTextOut == %s\n", dwInfEntryPointStatus, *pszTextOut);
   return dwInfEntryPointStatus;
}

extern "C" DWORD APIENTRY Update(DWORD const cArgs, char const * const apszArgs[], char const ** const pszTextOut)
{
   AFX_MANAGE_STATE(AfxGetStaticModuleState());

   TRACE("Entering Update()\n");

#if defined(_DEBUG)
   TRACE("Update arguments:\n");
   for (DWORD i = 0; i < cArgs; i++) TRACE("  apszArgs[%d] == %s\n", i, apszArgs[i]);
#endif

   DWORD dwInfEntryPointStatus = INFENTRYPOINT_FAILURE;
   *pszTextOut = g_pszINFTextOutError;

   try
   {
      UpdateSoftware();

      *pszTextOut = g_pszINFTextOutOK;
      dwInfEntryPointStatus = INFENTRYPOINT_SUCCESS;
   }
   catch (CConfigException ce)
   {
      ce.ReportError();
   }

   TRACE("Exiting Update(): dwInfEntryPointStatus == %d, *pszTextOut == %s\n", dwInfEntryPointStatus, *pszTextOut);
   return dwInfEntryPointStatus;
}



extern "C" DWORD APIENTRY AddModemSettingsToRAS(DWORD const cArgs, char const * const apszArgs[], char const ** const pszTextOut)
{
   AFX_MANAGE_STATE(AfxGetStaticModuleState());

   TRACE("Entering InstallModemInf()\n");

#if defined(_DEBUG)
   TRACE("InstallModemInf arguments:\n");
   for (DWORD i = 0; i < cArgs; i++) TRACE("  apszArgs[%d] == %s\n", i, apszArgs[i]);
#endif

   DWORD dwInfEntryPointStatus = INFENTRYPOINT_FAILURE;
   *pszTextOut = g_pszINFTextOutError;

   try
   {
      enum
      {
         ARG_WINDOWHANDLE,
         ARG_ADAPTERTYPE,
         ARG_COUNT,
      };

      AffirmThrow(cArgs == ARG_COUNT);
		AffirmThrow(AddModemToRAS(apszArgs[ARG_ADAPTERTYPE]));
      *pszTextOut = g_pszINFTextOutOK;
      dwInfEntryPointStatus = INFENTRYPOINT_SUCCESS;
   }
   catch (CConfigException ce)
   {
      ce.ReportError();
   }

   TRACE("Exiting InstallModemInf(): dwInfEntryPointStatus == %d, *pszTextOut == %s\n", dwInfEntryPointStatus, *pszTextOut);
   return dwInfEntryPointStatus;
}




/////////////////////////////////////////////////////////////////////////////
// The entry points into this DLL used by the INF under NT5.0

#if defined NT5
// length of registry instance as number of characters
#define REGINSTANCE_NUMCHARS	4

// Writes the specified line protocol for all of the adapter's lines
BOOL WriteAdapterLineProtocol(   HDEVINFO DeviceInfoSet,
                                 PSP_DEVINFO_DATA DeviceInfoData,
                                 CString const LineProtocol)
{
   BOOL Success = FALSE;
   try
   {
      HKEY hParamsKey;
      AffirmThrow(OpenAdapterParamsKey(DeviceInfoSet, DeviceInfoData, &hParamsKey));
      // Figure out how many lines we have and write the desired protocol for all of them
      {
         DWORD nIndex, nLines;
         CString strLine;
         HKEY hLineKey;
         AffirmThrow(RegReadDWordValue(hParamsKey, DXB_REGVAL_NUMBEROFLINES, &nLines));
         for (nIndex = 0; nIndex < nLines; nIndex++)
         {
            // NOTE: NOTE: Assumes lines are zero based integers...
            strLine.Format("%d", nIndex);
            AffirmThrow(RegCreateKey(hParamsKey, strLine, &hLineKey));
				AffirmThrow(RegWriteStrValue(hLineKey,DXB_REGVAL_PROTOCOL,LineProtocol));
            RegCloseKey(hLineKey);
         }
      }

      RegCloseKey(hParamsKey);
      Success = TRUE;
   }
	catch( CAffirmException ae )
	{
	}

   return Success;
}

void RemoveAdapterParams(HDEVINFO DeviceInfoSet,PSP_DEVINFO_DATA DeviceInfoData)
{
	TRACE("RemoveAdapterParams()\n");
   CString strAdapterType;
   CString strAdapterRegPath;
   {
      HKEY hKey;
      AffirmThrow(OpenAdapterParamsKey(DeviceInfoSet, DeviceInfoData, &hKey));
      AffirmThrow(RegReadStrValue(hKey,DXB_REGVAL_OPTION,strAdapterType));
      AffirmThrow(RegReadStrValue(hKey,DXB_REGVAL_REGPATH,strAdapterRegPath));
      RegCloseKey(hKey);
   }

   TRACE("    adapter = %s\n",strAdapterType);
   TRACE("    regpath = %s\n",strAdapterRegPath);

   CAdapter * pAdapter = NULL;
   AffirmThrow(NewConfigObject((CConfigObject **)(&pAdapter), CConfigObject::ADAPTER, strAdapterType, HKEY_LOCAL_MACHINE, strAdapterRegPath, NULL));
   pAdapter->RemoveParameters();
   DeleteConfigObject((CConfigObject **)(&pAdapter));
}

// Once the adapter is installed, the function obtains the device instance id
// (HKLM\CCS\Control\Class\ClassGuid\Instance). This 4 digit identifier is used
// to create a few unique strings which are then written to the registry
// section of our device. These unique strings our used for the purpose of
// distinguishing device objects created for different adapters.
// For the life of the device, these strings are fixed. 
// create the ServiceName value within the instance entry.
void WriteAdapterParams (HDEVINFO DeviceInfoSet,PSP_DEVINFO_DATA DeviceInfoData, BOOL fCreateAdapter /*=TRUE*/)
{
	TCHAR szInstance[MAX_PATH];
	DWORD dwReqSize=0;
	// get the DRIVER property. This is stored under HKLM\CCS\ENUM\ROOT\ 
	// its of the form ClassGuid\xxxx where xxxx is the instance # under
	// HKLM\System\CCS\Control\Class
	BOOL bResult = SetupDiGetDeviceRegistryProperty(DeviceInfoSet,DeviceInfoData,
						SPDRP_DRIVER,&dwReqSize,
						(PBYTE)&szInstance,sizeof(szInstance),NULL);
	if (bResult)
	{
		TRACE("Device Instance = %s\n",szInstance);
		CString szTemp(szInstance);
		// get the last 4 digits. This is the instance we are interested in
		// and concatenate to our service name
		szTemp = szTemp.Right(REGINSTANCE_NUMCHARS);
		// open the Device Instance key in the registry
		HKEY hRootKey =  SetupDiOpenDevRegKey(DeviceInfoSet, DeviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_ALL_ACCESS);
		if (hRootKey != (HKEY)INVALID_HANDLE_VALUE)
		{
			CString szServiceName;

			TRACE("DXB Instance Key Opened\n");

			// Get the Product name and derive the Service Name
			RegReadStrValue(hRootKey,DXB_REGVAL_PRODUCT_NAME,szServiceName);
			szServiceName += szTemp;
			TRACE("serviceName = %s\n",(LPCTSTR)szServiceName);

			HKEY hKey;

			// get to the parameters key
			if (RegOpenKey(hRootKey,DXB_REGKEY_PARAMETERS,&hKey))
			{
				TRACE("DXB Parameters key opened\n");
				RegWriteStrValue(hKey,DXB_REGVAL_SERVICENAME,szServiceName);
				RegWriteStrValue(hKey,DXB_REGVAL_NDIS_ADAPTER_INSTANCE,szTemp); 
				// build the entire path from HKLM to the device instance
				szTemp = DXB_REGCLASS_VALUE;
				szTemp += szInstance;
				szTemp += (_T("\\"));
				szTemp += DXB_REGKEY_PARAMETERS;
				RegWriteStrValue(hKey,DXB_REGVAL_REGPATH,szTemp);
				RegWriteStrValue(hKey,DXB_REGVAL_REGINSTANCE,szInstance);

            CString strAdapterType;
            CString strAdapterRegPath;
            {
               HKEY hParamsKey;
               AffirmThrow(OpenAdapterParamsKey(DeviceInfoSet, DeviceInfoData, &hParamsKey));
               AffirmThrow(RegReadStrValue(hParamsKey,DXB_REGVAL_OPTION,strAdapterType));
               AffirmThrow(RegReadStrValue(hParamsKey,DXB_REGVAL_REGPATH,strAdapterRegPath));
               RegCloseKey(hParamsKey);
            }
            TRACE("    adapter = %s\n",strAdapterType);
            TRACE("    regpath = %s\n",strAdapterRegPath);

            if (fCreateAdapter)
            {
               CAdapter * pAdapter = NULL;
               AffirmThrow(NewConfigObject((CConfigObject **)(&pAdapter), CConfigObject::ADAPTER, strAdapterType, HKEY_LOCAL_MACHINE, strAdapterRegPath, NULL));
               AffirmThrow(NULL != pAdapter);
               pAdapter->SetParameters();
               DeleteConfigObject((CConfigObject **)(&pAdapter));
            }

				RegCloseKey(hKey);
			}

         RegCloseKey(hRootKey);
		}
			
	}
}

// function either increments the reference count for WAN Links adapter being
// added or decrements if adapter is being removed.
// Once ref count goes down to 0, the entire WAN Links subtree is removed
// from under Software\Digi

typedef enum {ADDING,REMOVING} INSTALLACTION;

static void UpdateInstallationStatus(INSTALLACTION Action)
{
#define REFCOUNT	_T("REFCOUNT")

	try
	{
		AffirmThrow((Action == ADDING || Action == REMOVING));
		HKEY hkey;
		int nRefCount=0;
		// open the currentverison key
		AffirmThrow(RegOpenKey(HKEY_LOCAL_MACHINE,DXB_REGKEY_SOFTWARE_CURRENTVERSION,&hkey));
		RegReadIntValue(hkey,REFCOUNT,&nRefCount,0);
		if (Action == ADDING)
			++nRefCount;
		else
			--nRefCount;
		AffirmThrow(nRefCount >= 0);

		RegWriteIntValue(hkey,REFCOUNT,nRefCount);
		RegCloseKey(hkey);
		// if reference count == 0, this is the last adapter of type WanLinks
		// being removed. Remove the entire WAN Links tree under Software\Digi
		if (nRefCount==0)
		{
			TRACE("Removing %s KEY\n",DXB_REGKEY_SOFTWARE_WANLINKS);

			AffirmThrow(RegDelKey(HKEY_LOCAL_MACHINE,DXB_REGKEY_SOFTWARE_WANLINKS));
			// check if Digi key itself is empty
			AffirmThrow(RegOpenKey(HKEY_LOCAL_MACHINE,DXB_REGKEY_SOFTWARE_DIGI,&hkey));
			CStringArray astrSubKeys;
			RegEnumSubKeyNames(hkey,astrSubKeys);
			RegCloseKey(hkey);
			// if no enumerated subkeys under Digi, whack the Digi key
			if (astrSubKeys.GetSize()==0)
			{
				AffirmThrow(RegDelKey(HKEY_LOCAL_MACHINE,DXB_REGKEY_SOFTWARE_DIGI));
			}

         // Remove PAM Agent
         theApp.RemovePamA();
		}

	}
	catch(CAffirmException ae)
	{
		TRACE("Exception occured in UpdateInstallationStatus\n");
	}
}

// During Physical Adapter installation we append an adapter identifier
// to the end of the Driver Description.  This identifier is then used
// as a Prefix to our Client adapter descriptions (See the function
// WriteFriendlyName). 
//
// NOTE* - We tried to use the entire adapter description as a prefix
// for the client adapters.  This worked fine for LAN devices, but WAN
// devices had problems.  With the longer Client names the connection
// manager would not load iprtrmgr.dll.  We do not know the exact reason,
// and will need to work with MS to find out why.  For now we will use
// the adapter identifier as the client adapter prefix.
//
void AssignAnAdapterIdentifier(HDEVINFO DeviceInfoSet,
					   PSP_DEVINFO_DATA DeviceInfoData
					   )
{

	// Append the Service Name to the Adapter Name.
	TCHAR szInst[MAX_PATH];
	DWORD dwReqSize=0;
	CString strAdapterIdentifier;
	BOOL bResult;
	CString strInstance;
	CString strNewDriverDescription;

	// get the drivers instance #
	bResult = SetupDiGetDeviceRegistryProperty(DeviceInfoSet,
					DeviceInfoData,
					SPDRP_DRIVER,&dwReqSize,
					(PBYTE)&szInst[0],sizeof(szInst),NULL);

	if ( bResult)
	{
		strInstance = szInst;

		// We only need the Adapter Number
		TRACE("Dxb instance = %s\n",strInstance);
		CString strDigi = DXB_REGKEY_DIGI;
		strAdapterIdentifier = " (" + strDigi + strInstance.Right(REGINSTANCE_NUMCHARS) + ")";
		
	}

	// Append the Adapter Identifier to the Adapter Description
	TCHAR szDriverDesription[MAX_PATH];
	dwReqSize=0;
	// get the client drivers instance #
	bResult = SetupDiGetDeviceRegistryProperty(DeviceInfoSet,
					DeviceInfoData,
					SPDRP_DEVICEDESC,&dwReqSize,
					(PBYTE)&szDriverDesription[0],sizeof(szDriverDesription),NULL);
	if (bResult)
	{
		strNewDriverDescription = szDriverDesription + strAdapterIdentifier;
		SetupDiSetDeviceRegistryProperty(DeviceInfoSet,
										DeviceInfoData,
										SPDRP_DEVICEDESC,
										(CONST BYTE *) (LPCTSTR)strNewDriverDescription,
									   	strNewDriverDescription.GetLength()
										);
		SetupDiSetDeviceRegistryProperty(DeviceInfoSet,
										DeviceInfoData,
										SPDRP_FRIENDLYNAME,
										(CONST BYTE *) (LPCTSTR)strNewDriverDescription,
									   	strNewDriverDescription.GetLength()
										);
	}

	
	// Set the Adapter DriverDesc
	HKEY	hClassKey;
	CString strClassPath(DXB_REGCLASS_VALUE);
	strClassPath += strInstance;
	TRACE ("DIGIINF: ClassPath = %s\n",strClassPath);
	if (RegOpenKey(HKEY_LOCAL_MACHINE,strClassPath,&hClassKey))
	{
		// Set the Driver Description
		RegWriteStrValue(hClassKey,DXB_REGVAL_DRIVER_DESCRIPTION,strNewDriverDescription);
		RegCloseKey(hClassKey);

	}


}


// During Client Adapter installation we need to write the Friendly Name
// of the Adapter.  This allows the user to choose devices that map to
// specific adapters/ports/channels.
//
// The Friendly name is assigned by taking the Driver Desription Identifier
// (see the Function AssignAnAdapterIdentifier) for the card and appending 
// the Protocol/Service Registry ("Desc") value for the specific service.
//
// The Friendly name must be written to both the Client device description
// and the client friendly name.  If more than one FR PVC exists the connection
// manager uses the Friendly Name.  If only one exists it uses the Device
// Description.
//
// NOTE* - These Friendly SVC names don't appear in the connection manager until
//         the system is rebooted.  TAPI is not PnP capable.  We will need to
//         work with MS on a solution for this.
//
void WriteFriendlyName(HDEVINFO DeviceInfoSet,
					   PSP_DEVINFO_DATA DeviceInfoData,
					   CString strInstance,
					   CString strDxbPath
					   )
{
    HKEY	hkey;
	CString strDescription;

	CString strParentPath(DXB_REGCLASS_VALUE);
	strParentPath += strDxbPath;
	TRACE("Set Friendly name DXB is at %s\n",strParentPath);
	if (RegOpenKey(HKEY_LOCAL_MACHINE,strParentPath,&hkey))
	{
		// Get the Friendly Name for this Device, if it exists
		RegReadStrValue(hkey,DXB_REGVAL_DESCRIPTION,strDescription);
		
		RegCloseKey(hkey);
	}
									
	// Write the Device Desciption and the Friendly Name for the newly installed device
	if (!strDescription.IsEmpty())

	{

	   CString strDeviceDescription;
		
		// Get the DXB Adapter DriverDesc
		HKEY	hDxbClassKey;
		int		index = strDxbPath.Find(_T('\\'));
		CString strDxbClassPath(DXB_REGCLASS_VALUE);
		strDxbClassPath += strDxbPath.Left(index+1);
		strDxbClassPath += strDxbPath.Mid(index+1,REGINSTANCE_NUMCHARS);
		TRACE ("DIGIINF: DxbClassPath = %s\n",strDxbClassPath);
		if (RegOpenKey(HKEY_LOCAL_MACHINE,strDxbClassPath,&hDxbClassKey))
		{
			// Set the Driver Deescription
			RegReadStrValue(hDxbClassKey,DXB_REGVAL_DRIVER_DESCRIPTION,strDeviceDescription);
			RegCloseKey(hDxbClassKey);

		}

	   TRACE ("DIGIINF: DeviceDescrition In = %s\n",strDeviceDescription);
	   index = strDeviceDescription.Find(_T('('));
	   strDeviceDescription = strDeviceDescription.Mid(index,2+REGINSTANCE_NUMCHARS+sizeof(DXB_REGKEY_DIGI));	   

	   strDeviceDescription += " - ";
	   strDeviceDescription += strDescription;
	   TRACE ("DIGIINF: DeviceDescrition Out = %s, Index = %d\n",strDeviceDescription, index);
	   if (!SetupDiSetDeviceRegistryProperty(DeviceInfoSet,
										DeviceInfoData,
										SPDRP_DEVICEDESC,
										(CONST BYTE *) (LPCTSTR)strDeviceDescription,
									   	strDeviceDescription.GetLength()
										))
	   {
			TRACE ("DIGIINF: Set DeviceDescrition FAILED = %s\n",strDeviceDescription);

	   }
	   if (!SetupDiSetDeviceRegistryProperty(DeviceInfoSet,
										DeviceInfoData,
										SPDRP_FRIENDLYNAME,
										(CONST BYTE *) (LPCTSTR)strDeviceDescription,
									   	strDeviceDescription.GetLength()
										))
	   {
			TRACE ("DIGIINF: Set FriendlyName FAILED = %s\n",strDeviceDescription);

	   }

		// Set the Adapter DriverDesc
		HKEY	hClassKey;
		CString strClassPath(DXB_REGCLASS_VALUE);
		strClassPath += strInstance;
		TRACE ("DIGIINF: ClassPath = %s\n",strClassPath);
		if (RegOpenKey(HKEY_LOCAL_MACHINE,strClassPath,&hClassKey))
		{
			// Set the Driver Description
			RegWriteStrValue(hClassKey,DXB_REGVAL_DRIVER_DESCRIPTION,strDeviceDescription);
			RegCloseKey(hClassKey);

		}

	}

}

// show the coinstaller trace messages
#ifndef _DEBUG
#define PrintInstallFunction(a,b)

#else

void PrintInstallFunction(const char* pszPrefix,DI_FUNCTION InstallFunction)
{

	char *pDIFMSGS[] = {
	"UNKNOWN_DIF_FUNCTION_0",
	"DIF_SELECTDEVICE",
	"DIF_INSTALLDEVICE",
	"DIF_ASSIGNRESOURCES",
	"DIF_PROPERTIES",          
	"DIF_REMOVE",                          
	"DIF_FIRSTTIMESETUP",
	"DIF_FOUNDDEVICE",
	"DIF_SELECTCLASSDRIVERS",
	"DIF_VALIDATECLASSDRIVERS",
	"DIF_INSTALLCLASSDRIVERS",
	"DIF_CALCDISKSPACE",
	"DIF_DESTROYPRIVATEDATA",
	"DIF_VALIDATEDRIVER",
	"DIF_MOVEDEVICE",
	"DIF_DETECT",
	"DIF_INSTALLWIZARD",
	"DIF_DESTROYWIZARDDATA",
	"DIF_PROPERTYCHANGE",
	"DIF_ENABLECLASS",
	"DIF_DETECTVERIFY",
	"DIF_INSTALLDEVICEFILES",
	"DIF_UNREMOVE",
	"DIF_SELECTBESTCOMPATDRV",
	"DIF_ALLOW_INSTALL",
	"DIF_REGISTERDEVICE",
	"DIF_NEWDEVICEWIZARD_PRESELECT",
	"DIF_NEWDEVICEWIZARD_SELECT",
	"DIF_NEWDEVICEWIZARD_PREANALYZE",
	"DIF_NEWDEVICEWIZARD_POSTANALYZE",
	"DIF_NEWDEVICEWIZARD_FINISHINSTALL",
	"DIF_UNUSED1",
	"DIF_INSTALLINTERFACES",
	"DIF_DETECTCANCEL",
	"DIF_REGISTER_COINSTALLERS",
	"DIF_ADDPROPERTYPAGE_ADVANCED",
	"DIF_ADDPROPERTYPAGE_BASIC",
	"DIF_RESERVED1",
	"DIF_TROUBLESHOOTER",
	"UNKNOWN_DIF_FUNCTION",
	};

	unsigned int nummsgs = sizeof(pDIFMSGS)/sizeof(pDIFMSGS[0]);
	if (InstallFunction >= nummsgs)
		InstallFunction = nummsgs-1;

	TRACE("%s install function (%x) = %s\n",pszPrefix,InstallFunction,pDIFMSGS[InstallFunction]);
}

#endif

// Some adapters require the protocol to be set before any params are written
static BOOL AdapterRequiresProtocolQuery(HDEVINFO DeviceInfoSet,
                                         PSP_DEVINFO_DATA DeviceInfoData
                                         )
{
   BOOL bResult = FALSE;
   try
   {
      HKEY hParamsKey;
      DWORD nQueryUserForProtocol;
      AffirmThrow(OpenAdapterParamsKey(DeviceInfoSet, DeviceInfoData, &hParamsKey));
      AffirmThrow(RegReadDWordValue(hParamsKey, TEXT("QueryUserForProtocol"), &nQueryUserForProtocol));
      RegCloseKey(hParamsKey);
      bResult = (0 != nQueryUserForProtocol);
   }
	catch( CAffirmException ae )
	{
	}

   return bResult;
}

// The choice of protocols is adapter specific, the choices presented here may be a subset of those allowed
static BOOL GetAdapterProtocolChoices(HDEVINFO DeviceInfoSet,
                                      PSP_DEVINFO_DATA DeviceInfoData,
                                      CStringArray & astrProtocols
                                      )
{
   BOOL Success = FALSE;
   try
   {
      HKEY hParamsKey;
      AffirmThrow(OpenAdapterParamsKey(DeviceInfoSet, DeviceInfoData, &hParamsKey));
      AffirmThrow(RegReadMultiStrValue(hParamsKey, TEXT("ProtocolsToQuery"), astrProtocols));
      RegCloseKey(hParamsKey);
      Success = TRUE;
   }
	catch( CAffirmException ae )
	{
	}

   return Success;
}

// Some adapters have a complex config involving a wizard...
BOOL AdapterHasWizard(HDEVINFO DeviceInfoSet,
                             PSP_DEVINFO_DATA DeviceInfoData
                             )
{
   BOOL bResult = FALSE;
   HKEY hParamsKey = NULL;
   DWORD nHasWizard = 0;
   try
   {
      AffirmThrow(OpenAdapterParamsKey(DeviceInfoSet, DeviceInfoData, &hParamsKey));
      AffirmThrow(RegReadDWordValue(hParamsKey, DXB_REGVAL_HAS_WIZARD, &nHasWizard, 1));
      RegCloseKey(hParamsKey);
      bResult = (0 != nHasWizard);
   }
	catch( CAffirmException ae )
	{
      if (NULL != hParamsKey)
      {
         RegCloseKey(hParamsKey);
      }
	}

   return bResult;
}

BOOL AdapterDefaultRequiresPropChange(HDEVINFO DeviceInfoSet,
                                             PSP_DEVINFO_DATA DeviceInfoData
                                             )
{
   /*
      Most default adapter configurations require the bus driver to be restarted
        so child devices get enumerated and installed (i.e. issue DIF_PROPERTYCHANGE)
      Adapters that support ISDN should be left alone since the system (ISDN Wizard) or
        our protocol wiz page will deal with them.
      Configurations which default to disabled lines should also be left alone (no children)

      Returns the following based on the first line's currently configured protocol:
        FALSE - ISDN protocol (PRI, BRI)
        FALSE - No line protocol specified (Registry value not present)
        FALSE - No config available (Registry key not present)
        TRUE  - All else
   */

   BOOL bResult;
   HKEY hParamsKey = NULL;
   HKEY hLine0Key = NULL;

   try
   {
      if (TRUE == (bResult = OpenAdapterParamsKey(DeviceInfoSet, DeviceInfoData, &hParamsKey)))
      {
         // Interrogate the first line
         CString strProtocol;
		   CString strLine0;
         strLine0.Format("%c", DXB_REGKEY_LINE_ENUM_FIRST);
         if(TRUE == (bResult = RegOpenKey(hParamsKey,strLine0,&hLine0Key)))
         {
            RegReadStrValue(hLine0Key,DXB_REGVAL_PROTOCOL,strProtocol);
            if (TRUE == (bResult = !strProtocol.IsEmpty()))
            {
               // The string wasn't empty
               // For these protocols, another mechanism will issue DIF_PROPERTYCHANGE on our behalf
               bResult = (
                  (DXB_REGVAL_PROTOCOL_ENUM_PRI != strProtocol) &&
                  (DXB_REGVAL_PROTOCOL_ENUM_PRI_NFAS_MASTER != strProtocol) &&
                  (DXB_REGVAL_PROTOCOL_ENUM_PRI_NFAS_SLAVE != strProtocol) &&
                  (DXB_REGVAL_PROTOCOL_ENUM_BRI != strProtocol) &&
                  (DXB_REGVAL_PROTOCOL_ENUM_E1 != strProtocol)
               );
            }
            RegCloseKey(hLine0Key);
         }
         RegCloseKey(hParamsKey);
      }
   }
	catch( CAffirmException ae )
	{
	}

   return bResult;
}


//+-----------------------------------------------------------------------------
//
//  Function:   PostProcessInstallDevice
//
//  Purpose:    Handle INF copy during DIF_INSTALLDEVICE PostProcessing
//
//  NOTES:      This is based on example code provided directly by MS
//              Should be included in DDK docs at some point 4/17/2000
//
//  Arguments:
//      DeviceInfoSet     [in]
//      DeviceInfoData    [in]
//      Context           [in,out]
//
//  Returns:    NO_ERROR or an error code.
//

DWORD
PostProcessInstallDevice (
                    IN     HDEVINFO          DeviceInfoSet,
                    IN     PSP_DEVINFO_DATA  DeviceInfoData,
                    IN OUT PCOINSTALLER_CONTEXT_DATA Context
                    )
{
    DWORD                  Status = NO_ERROR;
    HINF                   InfFile = INVALID_HANDLE_VALUE;
    SP_DRVINFO_DATA        DriverInfoData;
    SP_DRVINFO_DETAIL_DATA DriverInfoDetailData;
    PSP_INF_INFORMATION    InfInformation = NULL;
    DWORD                  ReqSize;
    SP_ORIGINAL_FILE_INFO  InfOriginalFileInformation;
    TCHAR                  InstallSectionName[LINE_LEN];
    TCHAR                  CombinedPath[MAX_PATH*2];
    TCHAR                  FullSourcePath[MAX_PATH];
    PTSTR                  FileNamePtr;
    INFCONTEXT             InfLineContext;
    DWORD                  FieldIndex;
    DWORD                  FieldCount;
    DWORD                  RetVal;

#define LOCAL_TRACE(Text) TRACE(TEXT("PostProcessInstallDevice: " Text "\n"))
#define COPYINF_KEY TEXT("DigiCopyINF")

    //
    // Find name of INF and name of install section
    //
    LOCAL_TRACE("Enter");

    DriverInfoData.cbSize = sizeof(SP_DRVINFO_DATA);
    if (!SetupDiGetSelectedDriver( DeviceInfoSet,
                                   DeviceInfoData,
                                   &DriverInfoData)) {
        Status = GetLastError();
        LOCAL_TRACE("Fail: SetupDiGetSelectedDriver");
        goto clean;
    }

    DriverInfoDetailData.cbSize = sizeof(SP_DRVINFO_DETAIL_DATA);
    if (!SetupDiGetDriverInfoDetail(DeviceInfoSet,
                                    DeviceInfoData,
                                    &DriverInfoData,
                                    &DriverInfoDetailData,
                                    sizeof(SP_DRVINFO_DETAIL_DATA),
                                    NULL)) {
        Status = GetLastError();
        if (Status == ERROR_INSUFFICIENT_BUFFER) {
            //
            // We don't need the extended information.  Ignore.
            //
        } else {
            LOCAL_TRACE("Fail: SetupDiGetDriverInfoDetail");
            goto clean;
        }
    }

    //
    // We have InfFileName, open the INF
    //
    InfFile = SetupOpenInfFile(DriverInfoDetailData.InfFileName,
                               NULL,
                               INF_STYLE_WIN4,
                               NULL);
    if (InfFile == INVALID_HANDLE_VALUE) {
        Status = GetLastError();
        LOCAL_TRACE("Fail: SetupOpenInfFile");
        goto clean;
    }
    //
    // determine original filename to see if it has been
    // previously copied
    // this is long-winded, and is rarely an apropriate thing
    // to do. If we don't do it in this case, we'll try and
    // copy INF files that have already been copied and that
    // we might not have source for.
    //
    ReqSize = 0;
    if (!SetupGetInfInformation(InfFile,
                                INFINFO_INF_SPEC_IS_HINF,
                                NULL,
                                0,
                                &ReqSize)) {
        Status = GetLastError();
        LOCAL_TRACE("Fail: SetupGetInfInformation");
        goto clean;
    }
    InfInformation = (PSP_INF_INFORMATION)malloc(ReqSize);
    if (InfInformation == NULL) {
        Status = ERROR_NOT_ENOUGH_MEMORY;
        LOCAL_TRACE("Fail: SetupGetInfInformation - malloc");
        goto clean;
    }
    if (!SetupGetInfInformation(InfFile,
                                INFINFO_INF_SPEC_IS_HINF,
                                InfInformation,
                                ReqSize,
                                NULL)) {
        Status = GetLastError();
        LOCAL_TRACE("Fail: SetupGetInfInformation - 2nd call");
        goto clean;
    }
    InfOriginalFileInformation.cbSize = sizeof(InfOriginalFileInformation);
    if(!SetupQueryInfOriginalFileInformation(InfInformation,
                                     0,
                                     NULL,
                                     &InfOriginalFileInformation)) {
        Status = GetLastError();
        LOCAL_TRACE("Fail: SetupQueryInfOriginalFileInformation");
        goto clean;
    }

    //
    // determine location of current INF, which will be used
    // as root of inf's specified in CopyInf
    // but we also need FileNamePtr here to do an INF Name check.
    //

    RetVal = GetFullPathName(DriverInfoDetailData.InfFileName,
                             MAX_PATH,
                             CombinedPath,
                             &FileNamePtr);
    if (RetVal == 0 || RetVal > MAX_PATH) {
        Status = GetLastError();
        LOCAL_TRACE("Fail: GetFullPathName");
        goto clean;
    }

    if (lstrcmpi(FileNamePtr,
                 InfOriginalFileInformation.OriginalInfName)!=0) {
        //
        // we have determined that this is a copy of original INF
        // so we must have already processed CopyINF= bits
        //
        Status = NO_ERROR;
        LOCAL_TRACE("Skip processing of CopyINF (INF not at source location)");
        goto clean;
    }

    //
    // determine section used for the install which might be different
    // from the section name specified in DriverInfoDetailData.
    //
    if(!SetupDiGetActualSectionToInstall(InfFile,
                                         DriverInfoDetailData.SectionName,
                                         InstallSectionName,
                                         LINE_LEN,
                                         NULL,
                                         NULL)) {

        Status = GetLastError();
        LOCAL_TRACE("Fail: SetupDiGetActualSectionToInstall");
        goto clean;
    }

    //
    // we look for keyword "CopyInf" in the install section
    //
    if (SetupFindFirstLine(InfFile,
                           InstallSectionName,
                           COPYINF_KEY,
                           &InfLineContext)) {
        LOCAL_TRACE("Found at least one CopyInf entry");
        do {
            //
            // CopyInf = a.inf,b.inf,c.inf...
            // a.inf will be at index 1.
            //
            FieldCount = SetupGetFieldCount(&InfLineContext);
            for(FieldIndex = 1;FieldIndex<=FieldCount;FieldIndex++) {
                if(SetupGetStringField(&InfLineContext,
                                       FieldIndex,
                                       FileNamePtr,
                                       MAX_PATH,
                                       NULL)) {
                    //
                    // we have a listed INF, obtain canonical pathname
                    // for this INF
                    // (note that we assume the INF is relative
                    //  to original INF and is typically just a filename)
                    //
                    RetVal = GetFullPathName(CombinedPath,
                                             MAX_PATH,
                                             FullSourcePath,
                                             NULL);
                    if (RetVal == 0 || RetVal > MAX_PATH) {
                        Status = GetLastError();
                        LOCAL_TRACE("Fail: GetFullPathName");
                        //
                        // not fatal
                        //
                    } else if(!SetupCopyOEMInf(FullSourcePath,
                                    NULL,
                                    SPOST_PATH,
                                    0,
                                    NULL,
                                    0,
                                    NULL,
                                    NULL)) {
                        Status = GetLastError();
                        LOCAL_TRACE("Fail: SetupCopyOEMInf");
                        //
                        // not fatal
                        //
                    }
                } else {
                    Status = GetLastError();
                    LOCAL_TRACE("Fail: SetupGetStringField");
                    //
                    // not fatal
                    //
                }
            }

        } while (SetupFindNextMatchLine(&InfLineContext,
                                        COPYINF_KEY,
                                        &InfLineContext));
    }

    Status = NO_ERROR;

clean:

    if (InfInformation) {
        free(InfInformation);
    }
    if (InfFile) {
        SetupCloseInfFile(InfFile);
    }
    //
    // since what we are doing does not affect primary device installation
    // always return success
    //
    return NO_ERROR;

#undef LOCAL_TRACE 
}


// coinstaller handler
// We provide a coinstaller entry point (referenced in the INF file)
// This function is interested in installation, configuration,removal &
// upgrade of the device.
extern "C" DWORD PASCAL DigiDxbCoInstaller(DI_FUNCTION InstallFunction,
    HDEVINFO                  DeviceInfoSet,
    PSP_DEVINFO_DATA          DeviceInfoData,  
    PCOINSTALLER_CONTEXT_DATA Context
    )
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

   PrintInstallFunction("DigiDxbCoinstaller",InstallFunction);
	DWORD dwResult = NO_ERROR;
	try
	{
		AffirmThrow(Context != NULL);
		switch(InstallFunction)
		{
		case DIF_PROPERTYCHANGE:
			{
				// if any data needs to be rewritten to the registry
				// HKEY hRootKey = SetupDiOpenDevRegKey(DeviceInfoSet, DeviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_ALL_ACCESS);
				// RegCloseKey(hRootKey);
				dwResult = Context->InstallResult;
			}
			break;
	
		case DIF_INSTALLDEVICE:
			{
            if (DeviceInfoData)
				{
			      if (!Context->PostProcessing)
			      {
      				// on the way in, request post processing
				      if (DeviceInfoData)
						      dwResult = ERROR_DI_POSTPROCESSING_REQUIRED;

               }
			      else	// on the way out, do post processing
			      {
                  // Don't do any processing if the class installer failed...
                  if (Context->InstallResult == NO_ERROR)
                  {
						   // Update our reference count
						   UpdateInstallationStatus(ADDING);

						   // Assign the Adapter Identifier
						   AssignAnAdapterIdentifier(DeviceInfoSet,DeviceInfoData);

                     // Copy the inf files for our client drivers...
                     dwResult = PostProcessInstallDevice(DeviceInfoSet,DeviceInfoData,Context);
                  }
                  else
                  {
                     // If there was already an error, just return it
                     dwResult = Context->InstallResult;
                  }
					}
				}
			}
			break;

      case DIF_NEWDEVICEWIZARD_FINISHINSTALL:
			{
#if defined NO_WIZARD_ALLOWED
            if (AdapterRequiresProtocolQuery(DeviceInfoSet,DeviceInfoData))
            {
               TRACE("***** Adapter requires Protocol Query *****\n");

               // Get the protocols to query
               CStringArray astrProtocolsToQuery;
               AffirmThrow(GetAdapterProtocolChoices( DeviceInfoSet, DeviceInfoData, astrProtocolsToQuery));

               // Make sure we can install a proppage...
               SP_NEWDEVICEWIZARD_DATA WizData;
               WizData.ClassInstallHeader.cbSize = sizeof(WizData.ClassInstallHeader);
               AffirmThrow(SetupDiGetClassInstallParams(  DeviceInfoSet, DeviceInfoData, (PSP_CLASSINSTALL_HEADER)&WizData, sizeof(WizData), NULL));
               AffirmThrow(WizData.ClassInstallHeader.InstallFunction==DIF_NEWDEVICEWIZARD_FINISHINSTALL);
               AffirmThrow(WizData.NumDynamicPages < MAX_INSTALLWIZARD_DYNAPAGES);

               // Get the name of our adapter to put in the title of the wiz sheet
               CString AdapterIdentityString;
               {
                  TCHAR szDescription[MAX_PATH];
	               DWORD dwReqSize=0;
	               // get the Hardware ID string, it includes PCI and Vendor ID info
	               if (SetupDiGetDeviceRegistryProperty(DeviceInfoSet, DeviceInfoData, SPDRP_DEVICEDESC, &dwReqSize, (PBYTE)&szDescription, sizeof szDescription, NULL))
	               {
                     AdapterIdentityString = szDescription;
                  }
               }

               // We'll get DIF_DESTROYPRIVATEDATA when it's time to delete
               CDevPrivateData * pPrivateData = theApp.GetDevicePrivateData(DeviceInfoSet,DeviceInfoData);
		         AffirmThrow(NULL != pPrivateData);

               // If a page has already been created, we need to delete it and create a new one
               if (NULL != pPrivateData->m_pHwWizProtocolPropPage)
               {
                  delete pPrivateData->m_pHwWizProtocolPropPage;
               }
               pPrivateData->m_pHwWizProtocolPropPage = new CHwWizProtocolPropPage();
		         AffirmThrow(NULL != pPrivateData->m_pHwWizProtocolPropPage);
               pPrivateData->m_pHwWizProtocolPropPage->SetPropPageParams(DeviceInfoSet, DeviceInfoData, astrProtocolsToQuery, AdapterIdentityString);
               HRESULT hr = MMCPropPageCallback(&(pPrivateData->m_pHwWizProtocolPropPage->m_kludge_psp));
		         AffirmThrow(hr == S_OK);
               HPROPSHEETPAGE hProp = CreatePropertySheetPage(&(pPrivateData->m_pHwWizProtocolPropPage->m_kludge_psp));
            	AffirmThrow(hProp != NULL);

               // Install the proppage into the "Found New Hardware" Wizard
               WizData.DynamicPages[WizData.NumDynamicPages] = hProp;
               WizData.NumDynamicPages++;
               AffirmThrow(SetupDiSetClassInstallParams( DeviceInfoSet, DeviceInfoData, (PSP_CLASSINSTALL_HEADER)&WizData, sizeof(WizData)));
               // After we return, our Wiz page will be displayed in the "Found New Hardware" Wizard
               dwResult = Context->InstallResult;
            }
            else
            {
               // Write the adapter defaults based on what the INF has already specified
               WriteAdapterParams(DeviceInfoSet,DeviceInfoData);
               // At this point, most default adapter configurations require the bus driver to be restarted
               //   so child devices get enumerated and installed (i.e. issue DIF_PROPERTYCHANGE)
               // Adapters that support ISDN should be left alone since the system (ISDN Wizard) or
               //   our protocol wiz page will deal with them.
               // Configurations which default to disabled lines should also be left alone (no children)
               if (AdapterDefaultRequiresPropChange(DeviceInfoSet,DeviceInfoData))
               {
                  TRACE("DigiDxbCoInstaller(): About to issue DIF_PROPERTYCHANGE\n");
                  SP_PROPCHANGE_PARAMS pcp = { 0 };
                  pcp.ClassInstallHeader.cbSize = sizeof pcp.ClassInstallHeader;
                  pcp.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
                  pcp.StateChange = DICS_PROPCHANGE;
                  pcp.Scope = DICS_FLAG_GLOBAL;
                  if (SetupDiSetClassInstallParams(DeviceInfoSet, DeviceInfoData, &pcp.ClassInstallHeader, sizeof pcp))
                  {
                     SetupDiCallClassInstaller( DIF_PROPERTYCHANGE, DeviceInfoSet, DeviceInfoData);
                  }
               }
            }
         }
         break;

#else // #if defined NO_WIZARD_ALLOWED

         BOOL fSilent = FALSE; // TPS
         // Show the wizard unless we don't have one or this is a silent install...
         if (!fSilent && AdapterHasWizard(DeviceInfoSet, DeviceInfoData))
         {
            TRACE("********** Doing Wizard *************\n");
            // Only write the minimum (FALSE param), the wizard will fill in the rest
            WriteAdapterParams(DeviceInfoSet,DeviceInfoData, FALSE);

            // Make sure we can install a proppage...
            SP_NEWDEVICEWIZARD_DATA WizData;
            WizData.ClassInstallHeader.cbSize = sizeof(WizData.ClassInstallHeader);
            AffirmThrow(SetupDiGetClassInstallParams(  DeviceInfoSet, DeviceInfoData, (PSP_CLASSINSTALL_HEADER)&WizData, sizeof(WizData), NULL));
            AffirmThrow(WizData.ClassInstallHeader.InstallFunction==DIF_NEWDEVICEWIZARD_FINISHINSTALL);
            AffirmThrow(WizData.NumDynamicPages < MAX_INSTALLWIZARD_DYNAPAGES);

            // We'll get DIF_DESTROYPRIVATEDATA when it's time to delete
            CDevPrivateData * pPrivateData = theApp.GetDevicePrivateData(DeviceInfoSet,DeviceInfoData);
		      AffirmThrow(NULL != pPrivateData);

            // If a page has already been created, we need to delete it and create a new one
            if (NULL != pPrivateData->m_pHwWizDigiWizPage)
            {
               delete pPrivateData->m_pHwWizDigiWizPage;
            }
            pPrivateData->m_pHwWizDigiWizPage = new CHwWizDigiWizPage();
		      AffirmThrow(NULL != pPrivateData->m_pHwWizDigiWizPage);
            pPrivateData->m_pHwWizDigiWizPage->SetPropPageParams(DeviceInfoSet, DeviceInfoData, pPrivateData);
            HRESULT hr = MMCPropPageCallback(&(pPrivateData->m_pHwWizDigiWizPage->m_kludge_psp));
		      AffirmThrow(hr == S_OK);
            HPROPSHEETPAGE hProp = CreatePropertySheetPage(&(pPrivateData->m_pHwWizDigiWizPage->m_kludge_psp));
            AffirmThrow(hProp != NULL);

            // Install the proppage into the "Found New Hardware" Wizard
            WizData.DynamicPages[WizData.NumDynamicPages] = hProp;
            WizData.NumDynamicPages++;
            AffirmThrow(SetupDiSetClassInstallParams( DeviceInfoSet, DeviceInfoData, (PSP_CLASSINSTALL_HEADER)&WizData, sizeof(WizData)));
            // After we return, our Wiz page will be displayed in the "Found New Hardware" Wizard
            dwResult = Context->InstallResult;

            // Control is now handed over to the MS "Found New Hardware" Wizard...
         }
         else
         {
            // Write the adapter defaults based on what the INF has already specified
            WriteAdapterParams(DeviceInfoSet,DeviceInfoData);
            // At this point, most default adapter configurations require the bus driver to be restarted
            //   so child devices get enumerated and installed (i.e. issue DIF_PROPERTYCHANGE)
            // Adapters that support ISDN should be left alone since the system (ISDN Wizard) or
            //   our protocol wiz page will deal with them.
            // Configurations which default to disabled lines should also be left alone (no children)
            if (AdapterDefaultRequiresPropChange(DeviceInfoSet,DeviceInfoData))
            {
               TRACE("DigiDxbCoInstaller(): About to issue DIF_PROPERTYCHANGE\n");
               SP_PROPCHANGE_PARAMS pcp = { 0 };
               pcp.ClassInstallHeader.cbSize = sizeof pcp.ClassInstallHeader;
               pcp.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
               pcp.StateChange = DICS_PROPCHANGE;
               pcp.Scope = DICS_FLAG_GLOBAL;
               if (SetupDiSetClassInstallParams(DeviceInfoSet, DeviceInfoData, &pcp.ClassInstallHeader, sizeof pcp))
               {
                  SetupDiCallClassInstaller( DIF_PROPERTYCHANGE, DeviceInfoSet, DeviceInfoData);
               }
            }
         }
#endif // #if defined NO_WIZARD_ALLOWED
         }
         break;

		case DIF_REMOVE:
			{
				TRACE("DigiDxbCoInstaller : DIF_REMOVE\n");
            RemoveAdapterParams(DeviceInfoSet, DeviceInfoData);
            UpdateInstallationStatus(REMOVING);
				dwResult = Context->InstallResult;
			}
			break;

      case DIF_ADDPROPERTYPAGE_ADVANCED:
         {
				TRACE("DigiDxbCoInstaller : DIF_ADDPROPERTYPAGE_ADVANCED\n");

            // Get the request info and validate it
            SP_ADDPROPERTYPAGE_DATA AddPageData;
            AddPageData.ClassInstallHeader.cbSize = sizeof(AddPageData.ClassInstallHeader);
            if (!SetupDiGetClassInstallParams(DeviceInfoSet,DeviceInfoData,(PSP_CLASSINSTALL_HEADER)&AddPageData,sizeof(AddPageData),NULL))
               break; // If we can't get the params, we can't do anything...
            if ( (AddPageData.ClassInstallHeader.InstallFunction != DIF_ADDPROPERTYPAGE_ADVANCED) ||
                 (AddPageData.NumDynamicPages >= MAX_INSTALLWIZARD_DYNAPAGES) )
               break; // This is the wrong request or there's no room for the page

            // Check if we've already got a private data associated with this device instance
            // Delete existing dialog and create a new one so module state is correct
            CDevPrivateData * pPrivateData = theApp.GetDevicePrivateData(DeviceInfoSet, DeviceInfoData);
            AffirmThrow(NULL != pPrivateData);
            if (NULL != pPrivateData->m_pTree)
               delete pPrivateData->m_pTree;
		      pPrivateData->m_pTree = new CTreeDialog;
            pPrivateData->m_pTree->m_psp.dwFlags |= PSP_HASHELP;
		      pPrivateData->m_pTree->SetAppObject(&theApp);
		      pPrivateData->m_pTree->SetDevInfo(DeviceInfoSet,DeviceInfoData);
            HRESULT hr = MMCPropPageCallback(&pPrivateData->m_pTree->m_psp);
            HPROPSHEETPAGE hProp = CreatePropertySheetPage((PROPSHEETPAGE *) &(pPrivateData->m_pTree->m_psp));
		      if ((hr != S_OK) || (hProp == NULL))
            {
               // Something failed, whack the page
               delete pPrivateData->m_pTree;
               pPrivateData->m_pTree = NULL;
               break;
            }
            AddPageData.DynamicPages[AddPageData.NumDynamicPages] = hProp;
            AddPageData.NumDynamicPages++;
            if (!SetupDiSetClassInstallParams(DeviceInfoSet, DeviceInfoData, (PSP_CLASSINSTALL_HEADER)&AddPageData, sizeof(AddPageData)))
            {
               delete pPrivateData->m_pTree;
               pPrivateData->m_pTree = NULL;
               break;
            }

            dwResult = NO_ERROR;
         }
         break;

      case DIF_DESTROYPRIVATEDATA:
         {
				TRACE("DigiDxbCoInstaller : DIF_DESTROYPRIVATEDATA\n");
            theApp.RemoveDevicePrivateData(DeviceInfoSet, DeviceInfoData);
            dwResult = Context->InstallResult;
         }
         break;

		default:
			break;
		}
	}
	catch(CAffirmException ae)
	{
		dwResult = ERROR_DI_DONT_INSTALL;
	}

	TRACE("<<<<<<<<<<<< DigiDxbCoinstaller:dwResult = %x\n",dwResult);
	return dwResult;

}

// a common coinstaller for all DXB clients. During post installation process
// connect DXB to the client. The client's instance gets pointed to
// the DXB tree where data is stored for the client. The DXB tree
// gets the client's location under HKLM\CCS\Control\Class
extern "C" DWORD PASCAL DigiDxbClientCoInstaller(DI_FUNCTION InstallFunction,
    HDEVINFO                  DeviceInfoSet,
    PSP_DEVINFO_DATA          DeviceInfoData,  
    PCOINSTALLER_CONTEXT_DATA Context
    )
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());
	PrintInstallFunction("DigiDxbClientCoinstaller",InstallFunction);
	
	DWORD dwResult = NO_ERROR;
	CString strInstance;
	try
	{
		switch(InstallFunction)
		{
		case DIF_PROPERTYCHANGE:
			{
				//	HKEY hRootKey = SetupDiOpenDevRegKey(DeviceInfoSet, DeviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_ALL_ACCESS);
				// RegCloseKey(hRootKey);
				dwResult = Context->InstallResult;
			}
			break;
		
		case DIF_INSTALLDEVICE:
			{
				// if we are on the way in, request post processing
				if (!Context->PostProcessing)
				{
					if (DeviceInfoData)
						dwResult = ERROR_DI_POSTPROCESSING_REQUIRED;
				}
				else	// on the way out. do post processing
				{
					if (DeviceInfoData)
					{
						TCHAR szInst[MAX_PATH];
						DWORD dwReqSize=0;
						// get the client drivers instance #
						BOOL bResult = SetupDiGetDeviceRegistryProperty(DeviceInfoSet,
										DeviceInfoData,
										SPDRP_DRIVER,&dwReqSize,
								(PBYTE)&szInst[0],sizeof(szInst),NULL);
					
						if ( bResult)
						{
							strInstance = szInst;
							TRACE("Client instance = %s\n",strInstance);
							HKEY hkey;
							// 
							// Open the client's parameter key
							// 
							CString strDxbPath;
							CString strClientPath(DXB_REGCLASS_VALUE);
							strClientPath += strInstance;
							strClientPath += _T("\\");
							strClientPath += DXB_REGKEY_PARAMETERS;
							if (RegOpenKey(HKEY_LOCAL_MACHINE,strClientPath,&hkey))
							{
								// read from the INF the initial servicename string
								// this name should be along the lines of DIGIISDN,DIGILAN etc.
								// append to this name, the registry instance and write it back
								// out as the actual service name to the same value.
								// This process is only done during install time
								CString strServiceName;
								RegReadStrValue(hkey,DXB_REGVAL_SERVICENAME,strServiceName);
								if (!strServiceName.IsEmpty())
								{
									strServiceName += strInstance.Right(REGINSTANCE_NUMCHARS);
									RegWriteStrValue(hkey,DXB_REGVAL_SERVICENAME,strServiceName);
								}
					
								// read the regpath pointing to DXB that was written by the
								// DXB driver. This path must exist
								RegReadStrValue(hkey,DXB_REGVAL_REGPATH,strDxbPath);
								RegCloseKey(hkey);
					
							}
					
							// This string is empty because either DXB forgot to write the
							// regpath string or we couldn't open the registry key for the
							// client driver
							AffirmThrow(!strDxbPath.IsEmpty());
							
					
							// the RegPath is of the following form
							// {ClassGuid}\0017\Parameters\FRService\1\2
					
							TRACE("Regpath string being used = %s\n",strDxbPath);
							// NOTE: to do later. 
							// When dealing with DigiSOH, we should read the
							// adapter name from the instance of DigiSoh/DigiDxb
					
							// create the Adapter name that the client will use
							// to communicate with DXB
							// extract the DXB instance from the RegPath and suffix it
							// to the DXB name string
							// We have the 4 digit instance since its provided as part of RegPath
							int index = strDxbPath.Find(_T('\\'));
					
							CString strParentPath(DXB_REGCLASS_VALUE);
							strParentPath += strDxbPath;
							TRACE("DXB is at %s\n",strParentPath);
							if (RegOpenKey(HKEY_LOCAL_MACHINE,strParentPath,&hkey))
							{
								// in the parent's location write the child's instance #
								// this value can later be used to remove the child
								RegWriteStrValue(hkey,DXB_REGVAL_REGINSTANCE,strInstance);
								
								RegCloseKey(hkey);

							}

							// Write the Friendly name for this Device
							WriteFriendlyName(DeviceInfoSet,DeviceInfoData,strInstance,strDxbPath);
					
						}
			
					}

					dwResult = Context->InstallResult;
				}
			}
			break;

		case DIF_REMOVE:
			{
				TRACE("DigiDxbClientCoInstaller: DIF_REMOVE\n");		
				dwResult = Context->InstallResult;
			}
			break;
		default:
			break;
		}
	}

	catch(CAffirmException ae)
	{
		dwResult = ERROR_DI_DONT_INSTALL;
	}

	return dwResult;

}


#else


/////////////////////////////////////////////////////////////////////////////
// NT4 stubs of above routines


extern "C" DWORD PASCAL DigiDxbCoInstaller()
{
	ASSERT(FALSE);
	return 0;
}

extern "C" DWORD PASCAL DigiDxbClientCoInstaller()
{
	ASSERT(FALSE);
	return 0;
}



#endif
