/*****************************************************************************
* 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"
#include "PnpFilesAndIds.h"
#include "dxbreg.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()
{
#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(WANLINKS_NAME_PREFIX_LC_QUOT "help.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()
{
   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()
{
   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(
   CString const & serviceName)
{
   SC_HANDLE SCMHandle = NULL;
   SC_HANDLE ServiceHandle = NULL;

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

      ServiceHandle = OpenService(SCMHandle, serviceName, 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.");
            // lnb: We could possibly sleep for thirty seconds
            // waiting for the service to stop, but even then,
            // we could still exit without knowing if the service
            // stopper, so what's the point of waiting?
            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", serviceName);

         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(), WANLINKS_HELP_FILENAME_QUOT, 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()
{
   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

#define WANLINKS_REGKEY_SILENT_INSTALL TEXT("DoSilentInstall")
#define WANLINKS_REGKEY_UPDATING_TAG   TEXT("UpdatingDriver")

// 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);
               // This call will establish new COM numbers. 
               // We want to preserve existing COM numbers 
               // if upgrading from Wanlinks to a split driver.
               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(
   HDEVINFO                         DeviceInfoSet,
   PSP_DEVINFO_DATA                 DeviceInfoData,
   PDIGI_CO_INSTALLER_PRIVATE_DATA  PrivateData,
   INSTALLACTION                    Action)
{
   #define REFCOUNT _T("REFCOUNT")

   try
   {
      AffirmThrow( ( (ADDING == Action) && (NULL != PrivateData) ) 
                  || (REMOVING == Action) );

      HKEY hkey;

      int nRefCount = 0;

      // If this is a new installation of the split driver (which 
      // could mean we're upgrading from the WanLinks driver),
      // increment the driver's REFCOUNT value. This is also
      // where we decrement REFCOUNT, if this is an uninstallation.

      if (   (NULL == PrivateData)                             // if REMOVING
          || (FALSE == PrivateData->m_bDoUpdateDriver)         // if fresh install
          || (TRUE == PrivateData->m_bUpgradingFromWanlinks) ) // if updating from Wanlinks
      {

         // Open the currentverison key

         AffirmThrow(RegOpenKey(HKEY_LOCAL_MACHINE, DXB_REGKEY_SOFTWARE_CURRENTVERSION, &hkey));

         // Read and adjust the current value

         RegReadIntValue(hkey, REFCOUNT, &nRefCount, 0);
         if (ADDING == Action)
         {
            nRefCount++;
         }
         else // REMOVING == Action
         {
            nRefCount--;
         }
         AffirmThrow(nRefCount >= 0);

         // Record the new value

         AffirmThrow(RegWriteIntValue(hkey, REFCOUNT, nRefCount));

         RegCloseKey(hkey);

         // Uninstall the driver if it's no longer needed

         if (0 == nRefCount)
         {
            CleanupRegistryServicesKeys(WANLINKS_NAME_PREFIX_TEXT);
            CleanupRegistrySoftwareKey(DXB_REGKEY_SOFTWARE_WANLINKS);
            theApp.RemovePamA(WANLINKS_PAMA_SERVICE_NAME_TEXT);
         }

         // Cleanup the old WanLinks driver

         if (   (NULL != PrivateData)
             && (TRUE == PrivateData->m_bUpgradingFromWanlinks) )
         {

            // Cleanup any child device leftovers, etc.

            CleanupUninstalledWanlinksInstallation(DeviceInfoSet, DeviceInfoData, PrivateData);

            // Attend to the REFCOUNT value

            nRefCount = 0;

            // Open the old WanLinks driver's currentverison key

            AffirmThrow(RegOpenKey(HKEY_LOCAL_MACHINE, DXB_ORIGINAL_REGKEY_SOFTWARE_CURRENTVERSION, &hkey));

            // Read and decrement REFCOUNT

            AffirmThrow(RegReadIntValue(hkey, REFCOUNT, &nRefCount, 0));
            nRefCount--;
            AffirmThrow(nRefCount >= 0);

            // Record the new REFCOUNT

            AffirmThrow(RegWriteIntValue(hkey, REFCOUNT, nRefCount));

            RegCloseKey(hkey);

            // Completely uninstall the old WanLinks driver 
            // if it's no longer needed (i.e., 0 == REFCOUNT)

            if (0 == nRefCount)
            {
               CleanupRegistryServicesKeys(WANLINKS_ORIGINAL_NAME_PREFIX_TEXT);
               CleanupRegistrySoftwareKey(DXB_ORIGINAL_REGKEY_SOFTWARE_WANLINKS);
               theApp.RemovePamA(WANLINKS_ORIGINAL_PAMA_SERVICE_NAME_TEXT);
            }
         }
      }
   }
   catch(CAffirmException ae)
   {
      TRACE("Exception in UpdateInstallationStatus, GetLastError()=%d\n", GetLastError());
   }
}

// CleanupUninstalledWanlinksInstallation is called whenever a 
// device is updated from the Wanlinks driver to a split driver.
// When the adapter is updated to the new driver, Windows leaves
// old Wanlinks child device residue in the Registry. We need to 
// enumerate the class key to find these leftover devices and 
// then we need to uninstall them.
void CleanupUninstalledWanlinksInstallation(
   HDEVINFO                         DeviceInfoSet,
   PSP_DEVINFO_DATA                 DeviceInfoData,
   PDIGI_CO_INSTALLER_PRIVATE_DATA  PrivateData)
{

   #if defined(ACCELEPORT_XP_BUILD) || defined(ACCELEPORT_RAS_BUILD)
   
   try
   {

      // Remove the old Wanlinks asyn child devices.

      try
      {
         // We can identify a child device by two of its parameters --
         // first, its DriverDesc references the parent adapter's 
         // Instance number and, second, its Hardware Id begins with
         // "dxb\\digi" (e.g., "dxb\\digippp", "dxb\\digifr", etc.).
         
         // Fortunately, we haven't yet overwritten the old Wanlinks 
         // driver's FriendlyName with the new split driver friendly
         // name, so we can parse the existing value for the old
         // Wanlinks driver's instance number.

         TCHAR szInstance[MAX_PATH];
         DWORD dwRegDataType;

         AffirmThrow(
            SetupDiGetDeviceRegistryProperty(
               DeviceInfoSet,
               DeviceInfoData,
               SPDRP_FRIENDLYNAME,
               &dwRegDataType,
               (PBYTE)szInstance,
               sizeof(szInstance),
               NULL));

         // The instance number is at the end of the string in
         // parenthesis after the Digi string, i.e., given
         // "Digi AccelePort Ras (Digi0006)", the instance 
         // number is "0006"

         CString csInstance(szInstance);

         int posit = csInstance.Find(TEXT('('));
         AffirmThrow(-1 != posit);
         // add 1 to move past the left paren. and add 4 to move past the "Digi" prefix
         csInstance = csInstance.Mid(posit + 1 + _tcslen(WANLINKS_ORIGINAL_NAME_PREFIX_TEXT), REGINSTANCE_NUMCHARS);

         // Get the class GUID for the class to be enumerated.

         GUID portGuid;
         DWORD dwGuidSize;

         AffirmThrow(SetupDiClassGuidsFromName(PORTCLASS, &portGuid, 1, &dwGuidSize));

         // Get the device info set.

         HDEVINFO hDeviceInfoSet = NULL;

         hDeviceInfoSet = SetupDiGetClassDevs(&portGuid, NULL, NULL, DIGCF_PROFILE);
         AffirmThrow(INVALID_HANDLE_VALUE != hDeviceInfoSet);

         // Enumerate the class key.

         DWORD index = 0;

         SP_DEVINFO_DATA curDeviceInfoData;
         curDeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);

         while (SetupDiEnumDeviceInfo(hDeviceInfoSet, index, &curDeviceInfoData))
         {

            // Get the Hardware Id of this child device.

            TCHAR szHwId[MAX_PATH];

            if (SetupDiGetDeviceRegistryProperty(
                  hDeviceInfoSet,
                  &curDeviceInfoData,
                  SPDRP_HARDWAREID,
                  &dwRegDataType,
                  (PBYTE)szHwId,
                  sizeof(szHwId),
                  NULL))
            {

               // This is a Wanlinks child device if the Hardware Id 
               // is the old Wanlinks driver Hardware Id, "dxb\\digiasyn".

               if (0 == _tcsicmp(szHwId, WANLINKS_ORIGINAL_ASYN_PNPHWID_TEXT))
               {

                  // Get the child device's Registry key.

                  HKEY hChildDriverKey = 
                     SetupDiOpenDevRegKey(
                        hDeviceInfoSet, 
                        &curDeviceInfoData, 
                        DICS_FLAG_GLOBAL, 
                        0, 
                        DIREG_DRV, 
                        KEY_ALL_ACCESS);

                  if (INVALID_HANDLE_VALUE != hChildDriverKey)
                  {
                     BOOL bDoRemoveChild = FALSE;

                     // Open the Parameters key.

                     HKEY hParametersKey;

                     if (TRUE == RegOpenKey(hChildDriverKey, DXB_REGKEY_PARAMETERS, &hParametersKey))
                     {
                        CString csRegPath;

                        if (TRUE == RegReadStrValue(hParametersKey, DXB_REGVAL_REGPATH, csRegPath))
                        {
                           // The RegPath is similar to {4D36E972-...}\0006\Parameters\ASYN\0\0.
                           // Past the first backslash is the instance id, which we 
                           // compare against the adapter's instance id. If they match,
                           // we need to remove this device.
                           int index = csRegPath.Find(TEXT('\\'));

                           if (   (-1 != index)
                               && (0 == csInstance.Compare(
                                    // add 1 to move past the slash
                                    csRegPath.Mid(index + 1, REGINSTANCE_NUMCHARS))) )
                           {

                              // Remember to remove the child device on the way out.

                              bDoRemoveChild = TRUE;

                              // Remove the child's old Wanlinks SERIALCOMM 
                              // key, which is like, "\\Device\\DIGIASYN0003". 
                              // We can use the child's VAdapterNum as the 
                              // instance number (i.e., "0003" in the example).

                              CString csVAdapterNum;

                              if (TRUE == RegReadStrValue(hParametersKey, DXB_REGVAL_NDIS_ADAPTER_INSTANCE, csVAdapterNum))
                              {

                                 // Create the device name.

                                 CString csKey;
                                 csKey.Format("%s%s%s", DIGI_DEVICENAME_PREFIX_TEXT, DXB_ORIG_HDWID_ASYN, csVAdapterNum);

                                 // Remove the key from SERIALCOMM.

                                 HKEY hSerialcomm;

                                 if (TRUE == RegOpenKey(HKEY_LOCAL_MACHINE, WINNT_REGKEY_SERIALCOMM_TEXT, &hSerialcomm))
                                 {
                                    RegDeleteValue(hSerialcomm, csKey);
                                    RegCloseKey(hSerialcomm);
                                 }
                              }
                           }
                        }

                        RegCloseKey(hParametersKey);
                     }

                     RegCloseKey(hChildDriverKey);

                     // I'm not sure if the child key really needs to be closed
                     // before we remove the child device, but whay the hey.

                     if (TRUE == bDoRemoveChild)
                     {
                        SetupDiCallClassInstaller(DIF_REMOVE, hDeviceInfoSet, &curDeviceInfoData);
                     }
                  }
               }
            }

            index++;                // Continuing on...
         }

         SetupDiDestroyDeviceInfoList(hDeviceInfoSet);
      }
      catch(CAffirmException ae)
      {
         TRACE("Exception in CleanupUninstalledWanlinksInstallation nested try, GetLastError()=%d\n", GetLastError());
      }

      // Tag the adapter's driver key so we can identify the adapter
      // when we're installing its children (since we can't otherwise
      // identify a child's parent when installing the child device).

      HKEY hParamsKey;

      if (TRUE == OpenAdapterParamsKey(DeviceInfoSet, DeviceInfoData, &hParamsKey))
      {
         RegWriteDWordValue(hParamsKey, WANLINKS_REGKEY_UPDATING_TAG, 0);
         RegCloseKey(hParamsKey);
      }

      #if defined(ACCELEPORT_RAS_BUILD)

      HKEY hNewParamsKey = (HKEY)INVALID_HANDLE_VALUE;
      HKEY classRegKey = (HKEY)INVALID_HANDLE_VALUE;

      try
      {
         // The new AccelePort RAS driver installs itself as a Multi-port 
         // Serial device, whereas the old Wanlinks driver is installed as
         // a Net class device, so we need to migrate some settings from 
         // the old class key before removing it.

         // First, make sure we can open the new MPS class device's 
         // Parameters key.

         AffirmThrow(OpenAdapterParamsKey(DeviceInfoSet, DeviceInfoData, &hNewParamsKey));

         // We stored the instance number of the old Wanlinks adapter 
         // when we created our private data structure. Use that value
         // to find the old Wanlinks Net class device.

         // Get the class GUID for the class to be enumerated.

         GUID classGuid;
         DWORD dwGuidSize;

         AffirmThrow(SetupDiClassGuidsFromName(NETCLASS, &classGuid, 1, &dwGuidSize));

         // It would be ideal to enumerate the class using SetupDiEnumDeviceInfo,
         // but the old Wanlinks device won't be enumerated since it's no longer
         // valid. So we enumerate the key the old fashioned way -- by hand.

         classRegKey = SetupDiOpenClassRegKey(&classGuid, KEY_ALL_ACCESS);

         AffirmThrow(INVALID_HANDLE_VALUE != classRegKey);

         // Enumerate the class key.

         DWORD index = 0;

         TCHAR subkeyName[MAX_PATH + 1];

         while (ERROR_SUCCESS == RegEnumKey(classRegKey, index, &subkeyName[0], sizeof(subkeyName) / sizeof(TCHAR)))
         {                       // while (enumerating class key)

            HKEY subKey = (HKEY)INVALID_HANDLE_VALUE;
            HKEY hOldParamsKey = (HKEY)INVALID_HANDLE_VALUE;

            try
            {

               // Open the subkey.

               AffirmThrow(RegOpenKey(classRegKey, subkeyName, &subKey));  // (This is _not_ the Win32 API call!)

               // Get the Device Description of the current device.

               CString cstrDriverDesc;

               AffirmThrow(RegReadStrValue(subKey, DXB_REGVAL_DRIVER_DESCRIPTION, cstrDriverDesc));

               // This is a Wanlinks RAS net device if the driver 
               // desc matches the friendly name we stored earlier.

               if (0 != PrivateData->m_pcsFriendlyName->CompareNoCase(cstrDriverDesc))
               {
                  throw CAffirmException();
               }

               // We've identified the leftover Wanlinks RAS Net device.
               // Open its Parameters key.

               AffirmThrow(RegOpenKey(subKey, DXB_REGKEY_PARAMETERS, &hOldParamsKey));

               // CxPx the device's country code.

               DWORD countryCode;

               if ( (TRUE == RegValueExists(hOldParamsKey, DXB_REGVAL_MODEM_NATIONALITY)) 
                   && (TRUE == RegReadDWordValue(hOldParamsKey, DXB_REGVAL_MODEM_NATIONALITY, &countryCode)) )
               {
                  RegWriteDWordValue(hNewParamsKey, DXB_REGVAL_MODEM_NATIONALITY, countryCode);
               }

               // Now go through and move all the ports' COM numbers. We can 
               // use the NumberOfLines key to determine how many ports to touch.

               DWORD numberOfLines;

               AffirmThrow(RegValueExists(hOldParamsKey, DXB_REGVAL_NUMBEROFLINES));
               AffirmThrow(RegReadDWordValue(hOldParamsKey, DXB_REGVAL_NUMBEROFLINES, &numberOfLines));

               for (unsigned int lineNo = 0; lineNo < numberOfLines; lineNo++)
               {

                  HKEY hOldParamSubKey = (HKEY)INVALID_HANDLE_VALUE;
                  HKEY hNewParamSubKey = (HKEY)INVALID_HANDLE_VALUE;

                  try
                  {
                     // First, construct the subkey path.

                     CString csRegPath;

                     csRegPath.Format("%s\\%d\\0", DXB_REGVAL_LINE_SERVICE_ASYN, lineNo);

                     // I.e., csRegPath => "ASYN\\0\\0", so complete path is
                     // "HKLM\\SYSTEM\\CCS\\Control\\Class\\{[GUID]}\\[InstanceNum]\\Parameters\\ASYN\\0\\0"

                     // Open the old device's child device's key.

                     AffirmThrow(RegOpenKey(hOldParamsKey, csRegPath, &hOldParamSubKey));

                     // Open the new device's child's key.

                     AffirmThrow(RegCreateKey(hNewParamsKey, csRegPath, &hNewParamSubKey));

                     // CxPx the ComNumber key

                     DWORD comNumber;

                     AffirmThrow(RegValueExists(hOldParamSubKey, DXB_REGVAL_COM_NUMBER));
                     AffirmThrow(RegReadDWordValue(hOldParamSubKey, DXB_REGVAL_COM_NUMBER, &comNumber));

                     RegWriteDWordValue(hNewParamSubKey, DXB_REGVAL_COM_NUMBER, comNumber);

                     RegCloseKey(hOldParamSubKey);
                     RegCloseKey(hNewParamSubKey);
                  }
                  catch(CAffirmException ae)
                  {
                     TRACE("Exception in CleanupUninstalledWanlinksInstallation nested try, GetLastError()=%d\n", GetLastError());

                     if (INVALID_HANDLE_VALUE == hOldParamSubKey)
                        { RegCloseKey(hOldParamSubKey); }
                     if (INVALID_HANDLE_VALUE == hNewParamSubKey)
                        { RegCloseKey(hNewParamSubKey); }
                  }
               }

               RegCloseKey(hOldParamsKey);

               RegCloseKey(subKey);
               RegCloseKey(hNewParamsKey);

               // Whack the old device installation.

               RegDelKey(classRegKey, subkeyName);

               // All done -- there exists only one matching device.

               break;
            }
            catch(CAffirmException ae)
            {
               TRACE("Exception in CleanupUninstalledWanlinksInstallation nested try, GetLastError()=%d\n", GetLastError());

               if (INVALID_HANDLE_VALUE == subKey)
                  { RegCloseKey(subKey); }
               if (INVALID_HANDLE_VALUE == hOldParamsKey)
                  { RegCloseKey(hOldParamsKey); }
            }

            index++;             // moving on...
         }                       // while (enumerating class key)
      }
      catch(CAffirmException ae)
      {
         TRACE("Exception in CleanupUninstalledWanlinksInstallation nested try, GetLastError()=%d\n", GetLastError());

         if (INVALID_HANDLE_VALUE == hNewParamsKey)
            { RegCloseKey(hNewParamsKey); }
         if (INVALID_HANDLE_VALUE == classRegKey)
            { RegCloseKey(classRegKey); }
      }

      #endif // #if defined(ACCELEPORT_RAS_BUILD)

   }

   #elif defined(DATAFIRE_RAS_BUILD) || defined(DATAFIRE_SYNC_BUILD)

   try
   {

      // We can identify a child device by two of its parameters --
      // first, its DriverDesc references the parent adapter's 
      // Instance number and, second, its Hardware Id begins with
      // "dxb\\digi" (e.g., "dxb\\digippp", "dxb\\digifr", etc.).
      
      // Parse the parent adapter's Driver value for its instance number.

      TCHAR szDriver[MAX_PATH];
      DWORD dwRegDataType;

      AffirmThrow(
         SetupDiGetDeviceRegistryProperty(
            DeviceInfoSet,
            DeviceInfoData,
            SPDRP_DRIVER,
            &dwRegDataType,
            (PBYTE)szDriver,
            sizeof(szDriver),
            NULL));

      // Get the last 4 characters of the Driver value -- 
      // this is the instance number (e.g., "0006").

      CString szInstance(szDriver);
      szInstance = szInstance.Right(REGINSTANCE_NUMCHARS);

      // Get the class GUID for the class to be enumerated.

      GUID netGuid;
      DWORD dwGuidSize;

      AffirmThrow(SetupDiClassGuidsFromName(NETCLASS, &netGuid, 1, &dwGuidSize));

      // Get the device info set.

      HDEVINFO hDeviceInfoSet = NULL;

      hDeviceInfoSet = SetupDiGetClassDevs(&netGuid, NULL, NULL, DIGCF_PROFILE);
      AffirmThrow(INVALID_HANDLE_VALUE != hDeviceInfoSet);

      // Enumerate the class key.

      DWORD index = 0;

      SP_DEVINFO_DATA curDeviceInfoData;
      curDeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);

      while (SetupDiEnumDeviceInfo(hDeviceInfoSet, index, &curDeviceInfoData))
      {

         // Get the Hardware Id of this child device.

         TCHAR szHwId[MAX_PATH];

         if (SetupDiGetDeviceRegistryProperty(
               hDeviceInfoSet,
               &curDeviceInfoData,
               SPDRP_HARDWAREID,
               &dwRegDataType,
               (PBYTE)szHwId,
               sizeof(szHwId),
               NULL))
         {

            // This is a Wanlinks child device if the Hardware Id 
            // matches the Wanlinks child device prefix, "dxb\\digi".

            if (0 == _tcsnicmp(szHwId, WANLINKS_ORIGINAL_PNPHWID_PREFIX_TEXT, _tcslen(WANLINKS_ORIGINAL_PNPHWID_PREFIX_TEXT)))
            {
               TCHAR szDriverDesc[MAX_PATH];

               // Read the DeviceDesc key (how is this related to the DriverDesc 
               // key which appears in the Registry instead of DeviceDesc?).

               if (SetupDiGetDeviceRegistryProperty(
                     hDeviceInfoSet,
                     &curDeviceInfoData,
                     SPDRP_DEVICEDESC,
                     &dwRegDataType,
                     (PBYTE)szDriverDesc,
                     sizeof(szDriverDesc),
                     NULL))
               {

                  // The child device is named somthing like, 
                  // "(Digi0006) - Frame Relay Line 2 : ...".
                  // The Instance number appears in the parenthesis after 
                  // "Digi" ("0006" in the example above). If this value
                  // matches the parent adapter's Instance number, we need
                  // to uninstall this child device.

                  CString driverDesc(szDriverDesc);

                  int index = driverDesc.Find(TEXT('('));

                  if (   (-1 != index)
                      && (0 == szInstance.Compare(
                           // add 1 to move past the left paren. and add 4 to move past the prefix, "Digi"
                           driverDesc.Mid(index + 1 + _tcslen(WANLINKS_ORIGINAL_NAME_PREFIX_TEXT), REGINSTANCE_NUMCHARS))) )
                  {
                     BOOL success = SetupDiCallClassInstaller(DIF_REMOVE, hDeviceInfoSet, &curDeviceInfoData);
                  }
               }
            }
         }
         index++;                // Continuing on...
      }

      SetupDiDestroyDeviceInfoList(hDeviceInfoSet);
   }

   #else

   // One really should define one of the above macros,
   // so, without the following try block, this will
   // generate a syntax error.
   /*
   try
   {
   }
   */

   #endif

   catch(CAffirmException ae)
   {
      TRACE("Exception in CleanupUninstalledWanlinksInstallation, GetLastError()=%d\n", GetLastError());
   }

}

// CleanupRegistryServicesKeys is called when a driver's REFCOUNT 
// is decremented to zero. At this point, we can removes the 
// driver's the old services keys, HKLM\System\CCS\Services\DigiDxb, 
// \DigiPpp, etc. or, perhaps, HKLM\System\CCS\Services\DgdsDxb, etc.
void CleanupRegistryServicesKeys(
   CString const & driverPrefix) // i.e., "Digi", "Dgds", "Dgax", "Dgar"
{
   SC_HANDLE hScmDb = NULL;
   SC_HANDLE hService = NULL;

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

      // Enumerate the list of service name prefixes (i.e., 
      // "Asyn", "Dxb", "Ppp", etc.). We'll concatenate
      // each of these with the service name prefix to 
      // construct a series of device service names.

      static TCHAR const * const szServiceNamePostfixes[] =
      {
         WANLINKS_ASYN_POSTFIX_TEXT
         // If we delete the Dxb service, it won't be completed
         // removed until the user restarts Windows. Consequently,
         // If one installs the old Wanlinks driver, updates to 
         // the splitdriver, uninstalls the splitdriver, and then
         // re-installs the splitdriver, the installation fails.
         // Until we figure out a way around this, the following
         // should be left out of the list of services to delete.
         //,WANLINKS_DXB_POSTFIX_TEXT
         ,WANLINKS_ISDN_POSTFIX_TEXT
         ,WANLINKS_LAN_POSTFIX_TEXT
         ,WANLINKS_PCI_POSTFIX_TEXT
         ,WANLINKS_PPP_POSTFIX_TEXT
         ,WANLINKS_SNA_POSTFIX_TEXT
         ,WANLINKS_SOH_POSTFIX_TEXT
         ,WANLINKS_WANX_POSTFIX_TEXT
      };

      int nElements = sizeof(szServiceNamePostfixes) / sizeof(szServiceNamePostfixes[0]);

      for (int i = 0; i < nElements; i++)
      {
         try
         {
            BOOL suc;

            // Construct the service name.

            CString serviceName(driverPrefix);
            serviceName.Insert(serviceName.GetLength() + 1, szServiceNamePostfixes[i]);

            // Try to open the service.

            hService = OpenService(hScmDb, serviceName, SERVICE_ALL_ACCESS);
            if (NULL == hService)
            {                    // service doesn't exist; move on...
               continue;
            }

            // Stop the service. 
            // ControlService generally returns FALSE, as the service is 
            // already going to be inactive (ERROR_SERVICE_NOT_ACTIVE) for 
            // child devices, since they'll all been uninstalled. For some
            // reason, GetLastError() returns ERROR_INVALID_SERVICE_CONTROL
            // when I try to stop the Dxb service (lnb). Nonetheless, I include 
            // this code for completeness, whether or not it does anything.

            SERVICE_STATUS status = {0};
            suc = ControlService(hService, SERVICE_CONTROL_STOP, &status);

            // Delete the service. 
            // This call will clean up the Registry (HKLM\System\CCS\Services\...).

            AffirmThrow(DeleteService(hService));
         }
         catch(CAffirmException ae)
         {
            DWORD lastError = GetLastError();
            TRACE("Exception in CleanupRegistryServicesKeys nested for, GetLastError()=%d\n", lastError);
         }

         if (hService != NULL)
         {
            CloseServiceHandle(hService);
         }

      }
   }
   catch(CAffirmException ae)
   {
      DWORD lastError = GetLastError();
      TRACE("Exception in CleanupRegistryServicesKeys, GetLastError()=%d\n", lastError);
   }

   if (hScmDb != NULL)
   {
      CloseServiceHandle(hScmDb);
   }

}

// CleanupRegistrySoftwareKey is called when a driver's REFCOUNT 
// is decremented to zero. This means the driver's software key can 
// be removed. The driver's software key is found beneath the Digi 
// software key, HKLM\Software\Digi.
void CleanupRegistrySoftwareKey(
   CString const & strSubKey)
{

   // Remove the driver's software key.

   TRACE("Removing %s KEY\n", strSubKey);

   AffirmThrow(RegDelKey(HKEY_LOCAL_MACHINE, strSubKey));

   // Whack the Digi key if it's empty.

   HKEY hkey;
   CStringArray astrSubKeys;

   AffirmThrow(RegOpenKey(HKEY_LOCAL_MACHINE, DXB_REGKEY_SOFTWARE_DIGI, &hkey));
   RegEnumSubKeyNames(hkey, astrSubKeys);
   RegCloseKey(hkey);

   if (0 == astrSubKeys.GetSize())
   {
      AffirmThrow(RegDelKey(HKEY_LOCAL_MACHINE, DXB_REGKEY_SOFTWARE_DIGI));
   }

}

// 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
// WriteDeviceFriendlyName). 
//
// 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 WriteAdapterFriendlyName(
   HDEVINFO DeviceInfoSet,
   PSP_DEVINFO_DATA DeviceInfoData,
   PTCHAR DeviceClass)
{
   BOOL bResult;
   CString strFriendlyName;
   CString strInstance = TEXT("");
   TCHAR szDeviceDescription[MAX_PATH];

   DWORD dwMemberIndex = 0;
   DWORD dwDeviceInstanceId = 1;
   HDEVINFO EnumDeviceInfoSet;
   SP_DEVINFO_DATA EnumDeviceInfoData;
   TCHAR szEnumDeviceDescription[MAX_PATH];
   TCHAR szEnumFriendlyName[MAX_PATH];
   GUID ClassGuid;
   DWORD dwClassGuidSize;

   // Get our adapters Device Description
   bResult = SetupDiGetDeviceRegistryProperty(DeviceInfoSet,
                                              DeviceInfoData,
                                              SPDRP_DEVICEDESC,
                                              NULL,
                                              (PBYTE)&szDeviceDescription[0],
                                              sizeof(szDeviceDescription),
                                              NULL);
   if (!bResult)
   {
      TRACE("WriteAdapterFriendlyName: SetupDiGetDeviceRegistryProperty(DeviceDesc) failed with %d", GetLastError());
      return;
   }

   // Set our adapters initial Friendly Name
   strFriendlyName = szDeviceDescription;

   // Determine the number of instances of this adapter in this system
   memset(&EnumDeviceInfoData, 0, sizeof(SP_DEVINFO_DATA));
   EnumDeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);

   bResult = SetupDiClassGuidsFromName(DeviceClass, &ClassGuid, 1, &dwClassGuidSize);
   if (!bResult)
   {
      TRACE("WriteAdapterFriendlyName: SetupDiClassGuidsFromName(%s) failed with %d", DeviceClass, GetLastError());
      return;
   }

   EnumDeviceInfoSet = SetupDiGetClassDevs(&ClassGuid,
                                           NULL,
                                           NULL,
                                           0);
   if (!EnumDeviceInfoSet)
   {
      TRACE("WriteAdapterFriendlyName: SetupDiGetClassDevs failed with %d", GetLastError());
      return;
   }

   while (SetupDiEnumDeviceInfo(EnumDeviceInfoSet,
                                dwMemberIndex,
                                &EnumDeviceInfoData))
   {
      bResult = SetupDiGetDeviceRegistryProperty(EnumDeviceInfoSet,
                                                 &EnumDeviceInfoData,
                                                 SPDRP_DEVICEDESC,
                                                 NULL,
                                                 (PBYTE)&szEnumDeviceDescription[0],
                                                 sizeof(szEnumDeviceDescription),
                                                 NULL);
      if (bResult)
      {
         // See if this Device Description matches with our adapters Device Description
         if (_tcscmp(szEnumDeviceDescription, szDeviceDescription) == 0)
         {
            // Match! Read this adapters Friendly Name
            bResult = SetupDiGetDeviceRegistryProperty(EnumDeviceInfoSet,
                                                       &EnumDeviceInfoData,
                                                       SPDRP_FRIENDLYNAME,
                                                       NULL,
                                                       (PBYTE)&szEnumFriendlyName[0],
                                                       sizeof(szEnumFriendlyName),
                                                       NULL);
            if (bResult)
            {
               // Found it! Build our adapters Friendly Name
               if (dwDeviceInstanceId > 1)
               {
                  // Build the instance string, if necessary
                  strInstance.Format(" #%d", dwDeviceInstanceId);
               }

               // Append the instance string, e.g. "Digi AccelePort RAS 8 Adapter" + " #2"
               strFriendlyName = szDeviceDescription + strInstance;

               // See if this Friendly Name matches with our adapters Friendly Name
               if (_tcscmp(szEnumFriendlyName, (LPCTSTR)strFriendlyName) == 0)
               {
                  // This Friendly Name is already in use, proceed
                  dwDeviceInstanceId++;
               }
            }
         }
      }

      dwMemberIndex++;
   }

   // Build our adapters Friendly Name (again)
   if (dwDeviceInstanceId > 1)
   {
      // Build the instance string, if necessary
      strInstance.Format(" #%d", dwDeviceInstanceId);
   }

   // Append the instance string, e.g. "Digi AccelePort RAS 8 Adapter" + " #2"
   strFriendlyName = szDeviceDescription + strInstance;

   if (!strFriendlyName.IsEmpty())
   {
      // Write it as the Friendly Name to the registry. Don't modify the Device Description.
      bResult = SetupDiSetDeviceRegistryProperty(DeviceInfoSet,
                                                 DeviceInfoData,
                                                 SPDRP_FRIENDLYNAME,
                                                 (CONST BYTE *)(LPCTSTR)strFriendlyName,
                                                 strFriendlyName.GetLength());
      if (!bResult)
      {
         TRACE("WriteAdapterFriendlyName: SetupDiSetDeviceRegistryProperty(FriendlyName) failed with %d", GetLastError());
         return;
      }
   }
}

// PreparePortNameForClassInstaller checks to see if the device
// is being updated from an old Wanlinks driver installation, and,
// if so, retains its existing COM number by reading it from the 
// adapter driver key and writing it to the new child device node
// under HKLM\SYSTEM\CCS\Enum\DGAXDXB\DGAXASYN, etc.
void PreparePortNameForClassInstaller(
   HDEVINFO          DeviceInfoSet,
   PSP_DEVINFO_DATA  DeviceInfoData)
{

   #if defined(ACCELEPORT_XP_BUILD) || defined(ACCELEPORT_RAS_BUILD)

   try
   {

      // We identify this port's COM number by reading it from
      // the parent adapter's driver key. We can identify the 
      // parent adapter because we tagged its key in the fcn.
      // CleanupUninstalledWanlinksInstallation, which was 
      // called when the parent adapter was updated.

      // Note that this is rather kludgy -- we cannot use either
      // the HDEVINFO or SP_DEVINFO_DATA structures to identify
      // this device's parent, so instead, we tag the parent
      // when it's updated, looking for that parent now to find
      // this device's COM number.
      
      // As such, an update may possibly fail in the following 
      // situations --
      // a. Updating a disabled device;
      // b. Updating more than one adapter simultaneously;


      // First, we search for the tagged adapter by enumerating
      // its class.

      GUID classGuid;
      DWORD dwGuidSize;

      AffirmThrow(SetupDiClassGuidsFromName(MPSCLASS, &classGuid, 1, &dwGuidSize));

      // Get the device info set.

      HDEVINFO hDeviceInfoSet = NULL;

      hDeviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL, DIGCF_PROFILE);
      AffirmThrow(INVALID_HANDLE_VALUE != hDeviceInfoSet);

      // Enumerate the class key.

      DWORD index = 0;

      SP_DEVINFO_DATA curDeviceInfoData;
      curDeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);

      while (SetupDiEnumDeviceInfo(hDeviceInfoSet, index, &curDeviceInfoData))
      {

         // Get the Hardware Id of the current device.

         TCHAR szHwId[MAX_PATH];
         DWORD dwRegDataType;

         if (SetupDiGetDeviceRegistryProperty(
               hDeviceInfoSet,
               &curDeviceInfoData,
               SPDRP_HARDWAREID,
               &dwRegDataType,
               (PBYTE)szHwId,
               sizeof(szHwId),
               NULL))
         {

            // This is a Wanlinks asyn device if the hardware Id 
            // matches one the Wanlinks asyn device hardware Ids.

            BOOL bIsWanlinksDevice = FALSE;

            TCHAR * ids[] =
            {
               WANLINKS_ID_LIST
            };

            for (int jndex = 0; jndex < ElementCount(ids); jndex++)
            {
               if (0 == _tcsnicmp(szHwId, ids[jndex], _tcslen(PNPHWID)))
               {
                  bIsWanlinksDevice = TRUE;
                  break;
               }
            }

            if (TRUE == bIsWanlinksDevice)
            {

               // We've identified a Wanlinks Asyn adapter.
               // Look for the update tag to see if this
               // adapter is the child device's parent.

               HKEY hParamsKey;

               if (TRUE == OpenAdapterParamsKey(hDeviceInfoSet, &curDeviceInfoData, &hParamsKey))
               {

                  DWORD updateTag;

                  if ( (TRUE == RegValueExists(hParamsKey, WANLINKS_REGKEY_UPDATING_TAG)) 
                      && (TRUE == RegReadDWordValue(hParamsKey, WANLINKS_REGKEY_UPDATING_TAG, &updateTag)) )
                  {

                     HKEY hDeviceParamKey = (HKEY)INVALID_HANDLE_VALUE;
                     HKEY hChildDeviceKey = (HKEY)INVALID_HANDLE_VALUE;

                     try
                     {

                        // The updateTag specifies the subkey from which to grab
                        // the COM number. Use it to construct the subkey path.

                        CString csRegPath;

                        csRegPath.Format("%s\\%d\\0", DXB_REGVAL_LINE_SERVICE_ASYN, updateTag);

                        // I.e., csRegPath => "ASYN\\0\\0", so complete path is
                        // "HKLM\\SYSTEM\\CCS\\Control\\Class\\{[GUID]}\\[InstanceNum]\\Parameters\\ASYN\\0\\0"

                        // Open the child device's parameter key, stored within the parent.

                        AffirmThrow(TRUE == RegOpenKey(hParamsKey, csRegPath, &hDeviceParamKey));

                        // Read the child device's ComNumber value.

                        DWORD comNumber;
                        AffirmThrow(RegReadDWordValue(hDeviceParamKey, DXB_REGVAL_COM_NUMBER, &comNumber));
                        AffirmThrow(0 < comNumber);

                        RegCloseKey(hDeviceParamKey);
                        hDeviceParamKey = (HKEY)INVALID_HANDLE_VALUE;

                        // Create the child device key.

                        HKEY hChildDeviceKey = 
                           SetupDiCreateDevRegKey(
                              DeviceInfoSet, 
                              DeviceInfoData, 
                              DICS_FLAG_GLOBAL, 
                              0, 
                              DIREG_DEV,
                              NULL,
                              NULL);

                        AffirmThrow(INVALID_HANDLE_VALUE != hChildDeviceKey);

                        // Write the PortName to the child device key.

                        CString portname;
                        portname.Format(TEXT("COM%d"), comNumber);

                        AffirmThrow(TRUE == RegWriteStrValue(hChildDeviceKey, DXB_REGVAL_ASYN_PORTNAME, portname));

                        // Close the child device key. 

                        RegCloseKey(hChildDeviceKey);
                        hChildDeviceKey = (HKEY)INVALID_HANDLE_VALUE;
                     }
                     catch(CAffirmException ae)
                     {
                        DWORD lastError = GetLastError();
                        TRACE("Exception in PreparePortNameForClassInstaller nested try, GetLastError()=%d\n", lastError);

                        if (INVALID_HANDLE_VALUE != hDeviceParamKey)
                        {
                           RegCloseKey(hDeviceParamKey);
                        }
                        if (INVALID_HANDLE_VALUE != hChildDeviceKey)
                        {
                           RegCloseKey(hChildDeviceKey);
                        }
                     }

                     // Increment the updateTag value so the next child device
                     // grabs the correct COM number.

                     updateTag++;

                     DWORD nLines = -1;
                     RegReadDWordValue(hParamsKey, DXB_REGVAL_NUMBEROFLINES, &nLines);

                     // If we've incremented past the number of lines, we're done,
                     // so remove the key.
                     if (updateTag >= nLines)
                     {
                        RegDelValue(hParamsKey, WANLINKS_REGKEY_UPDATING_TAG);
                     }
                     // Otherwise, increment the value for the next child device 
                     // to be installed.
                     else
                     {
                        RegWriteDWordValue(hParamsKey, WANLINKS_REGKEY_UPDATING_TAG, updateTag);
                     }

                  }

                  RegCloseKey(hParamsKey);
               }
            }
         }

         index++;                // Continuing on...
      }

      SetupDiDestroyDeviceInfoList(hDeviceInfoSet);
   }

   #else

   try
   {
   }

   #endif

   catch(CAffirmException ae)
   {
      TRACE("Exception in PreparePortNameForClassInstaller, GetLastError()=%d\n", GetLastError());
   }

}

// CleanupRegistrySerialcommKey is called by the child device
// co-installer to remove the child device's entry from the
// Windows Serialcomm Registry key.
void CleanupRegistrySerialcommKey(
   HDEVINFO          DeviceInfoSet,
   PSP_DEVINFO_DATA  DeviceInfoData)
{

   #if defined(ACCELEPORT_XP_BUILD) || defined(ACCELEPORT_RAS_BUILD)

   HKEY hChildDriverKey = (HKEY)INVALID_HANDLE_VALUE;
   HKEY hParametersKey = (HKEY)INVALID_HANDLE_VALUE;
   HKEY hSerialcommKey = (HKEY)INVALID_HANDLE_VALUE;

   try
   {

      // Get the child device's Registry key.

      hChildDriverKey = 
         SetupDiOpenDevRegKey(
            DeviceInfoSet, 
            DeviceInfoData, 
            DICS_FLAG_GLOBAL, 
            0, 
            DIREG_DRV, 
            KEY_ALL_ACCESS);

      AffirmThrow(INVALID_HANDLE_VALUE != hChildDriverKey);

      // Open the Parameters key.

      AffirmThrow(TRUE == RegOpenKey(hChildDriverKey, DXB_REGKEY_PARAMETERS, &hParametersKey));

      RegCloseKey(hChildDriverKey);

      // Get the instance number string (e.g., "0003").

      CString csVAdapterNum;

      AffirmThrow(RegReadStrValue(hParametersKey, DXB_REGVAL_NDIS_ADAPTER_INSTANCE, csVAdapterNum));

      RegCloseKey(hParametersKey);

      // Create the device name (e.g., "\\Device\\DGAXASYN0003").

      CString csKey;

      csKey.Format("%s%s%s", DIGI_DEVICENAME_PREFIX_TEXT, DXB_HDWID_ASYN, csVAdapterNum);

      // Remove the key from SERIALCOMM.

      AffirmThrow(TRUE == RegOpenKey(HKEY_LOCAL_MACHINE, WINNT_REGKEY_SERIALCOMM_TEXT, &hSerialcommKey));

      RegDeleteValue(hSerialcommKey, csKey);

      RegCloseKey(hSerialcommKey);
   }
   catch(CAffirmException ae)
   {
      TRACE("Exception in CleanupRegistrySerialcommKey, GetLastError()=%d\n", GetLastError());
   }

   if (INVALID_HANDLE_VALUE != hSerialcommKey)
   {
      RegCloseKey(hSerialcommKey);
   }

   if (INVALID_HANDLE_VALUE != hParametersKey)
   {
      RegCloseKey(hParametersKey);
   }

   if (INVALID_HANDLE_VALUE != hChildDriverKey)
   {
      RegCloseKey(hChildDriverKey);
   }

   #endif

}

// 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 Description Identifier
// (see the function WriteAdapterFriendlyName) 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 WriteDeviceFriendlyName(
   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 Device Description for this device, if it exists
      RegReadStrValue(hkey, DXB_REGVAL_DESCRIPTION, strDescription);
      RegCloseKey(hkey);
   }
                           
   // Write 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 Description
         RegReadStrValue(hDxbClassKey, DXB_REGVAL_DRIVER_DESCRIPTION, strDeviceDescription);
         RegCloseKey(hDxbClassKey);
      }

      TRACE ("DIGIINF: DeviceDescrition In = %s\n", strDeviceDescription);
      index = strDeviceDescription.Find(_T('('));
      // index should be -1 in the "split driver", because the new  
      // device desc naming scheme doesn't put the instance # in parans.
      // ** NOTE ** 
      // A comment above the definition of WriteAdapterFriendlyName
      // mentions that long driver names may screw up our child devices.
      // So should the split drivers truncate the adapter name or use it whole??
      if (-1 != index)
      {
         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_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)
{
   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 
}


// CompleteModemClassUpdate searches the modem class key for
// an older modem class installation, migrates its user-settable
// parameters to the new modem class installation and deletes 
// its entry from the class key.
void CompleteModemClassUpdate(
   HDEVINFO          DeviceInfoSet,
   PSP_DEVINFO_DATA  DeviceInfoData)
{
   #if defined(ACCELEPORT_RAS_BUILD)

   // Our goal is to find the ole Wanlinks driver's modem device,
   // copy its settings to the new split driver's modem device,
   // and then delete the old Wanlinks modem keys.

   // Our strategy is to enumerate the modem class key looking 
   // for the modem that uses the same COM number as the port.
   // We can distinguish between the new device and the old
   // device by inspecting the Hardware Id.

   HKEY hDriverKey = (HKEY)INVALID_HANDLE_VALUE;

   try
   {

      // First, read the new device's "AttachedTo" key (which is 
      // the PortName of the device to which the modem is attached).

      // Open the device key.

      hDriverKey = 
         SetupDiOpenDevRegKey(
            DeviceInfoSet, 
            DeviceInfoData, 
            DICS_FLAG_GLOBAL, 
            0, 
            DIREG_DRV, 
            KEY_ALL_ACCESS);

      AffirmThrow(INVALID_HANDLE_VALUE != hDriverKey);

      // Next, read the AttachedTo key.

      CString cstrAttachedTo;

      AffirmThrow(RegValueExists(hDriverKey, DXB_REGVAL_MODEM_ATTACHED_TO));
      AffirmThrow(RegReadStrValue(hDriverKey, DXB_REGVAL_MODEM_ATTACHED_TO, cstrAttachedTo));

      // Next, enumerate the modem class Registry branch.

      // Get the class GUID for the class to be enumerated.

      GUID classGuid;
      DWORD dwGuidSize;

      AffirmThrow(SetupDiClassGuidsFromName(MODEMCLASS, &classGuid, 1, &dwGuidSize));

      // Get the device info set.

      HDEVINFO hDeviceInfoSet = NULL;

      hDeviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL, DIGCF_PROFILE);
      AffirmThrow(INVALID_HANDLE_VALUE != hDeviceInfoSet);

      // Enumerate the class key.

      DWORD index = 0;

      SP_DEVINFO_DATA curDeviceInfoData;
      curDeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);

      while (SetupDiEnumDeviceInfo(hDeviceInfoSet, index, &curDeviceInfoData))
      {                          // while (enumerating class key)

         HKEY hChildDriverKey = (HKEY)INVALID_HANDLE_VALUE;

         try
         {

            // Open the current device's device key.

            hChildDriverKey = 
               SetupDiOpenDevRegKey(
                  hDeviceInfoSet, 
                  &curDeviceInfoData, 
                  DICS_FLAG_GLOBAL, 
                  0, 
                  DIREG_DRV, 
                  KEY_ALL_ACCESS);

            if (INVALID_HANDLE_VALUE == hChildDriverKey)
            {
               throw CAffirmException();
            }

            // Read the current device's "AttachedTo" key -- this will 
            // match the value we read before if this is (a.) the old
            // Wanlinks driver's modem or (b.) the new split driver's
            // modem (in which we skip to the next enumeration).
            // (Note that, generally, one should never end a sentence
            //  with a preposition -- so it should really be named,
            //  "ToWhichAttached".)

            CString cstrChildAttachedTo;

            AffirmThrow(RegValueExists(hChildDriverKey, DXB_REGVAL_MODEM_ATTACHED_TO));
            AffirmThrow(RegReadStrValue(hChildDriverKey, DXB_REGVAL_MODEM_ATTACHED_TO, cstrChildAttachedTo));

            if (0 != cstrChildAttachedTo.CompareNoCase(cstrAttachedTo))
            {                    // not the modem we're looking for
               throw CAffirmException();
            }                    // not the modem for which we're looking

            // Inspect the Hardware Id to determine if this is the new
            // modem device or the old modem device.

            TCHAR szHardwareId[MAX_PATH+1];
            DWORD dwRegDataType;

            AffirmThrow(
               SetupDiGetDeviceRegistryProperty(
                  hDeviceInfoSet,
                  &curDeviceInfoData,
                  SPDRP_HARDWAREID,
                  &dwRegDataType,
                  (PBYTE)szHardwareId,
                  sizeof(szHardwareId),
                  NULL));

            if (0 == _tcsicmp(szHardwareId, "SERENUM\\DBA0001"))
            {                    // same device as the one being installed
               throw CAffirmException();
            }

            AffirmThrow(0 == _tcsicmp(szHardwareId, "SERENUM\\DBI0001"));

            // Good -- if we're here, this is the old modem device.
            // All that's left is to migrate its settings and then
            // to delete it.

            // We use a lookup to assist in the migration process.

            static const struct
            {
               TCHAR * szKeyName;
               DWORD   dwRegType;
            }
            lookup[] =
            {
                { DXB_REGVAL_MODEM_DCB,                 REG_BINARY }
               ,{ DXB_REGVAL_MODEM_DEFAULT,             REG_BINARY }
               ,{ DXB_REGVAL_MODEM_LOGGING,             REG_BINARY }
               ,{ DXB_REGVAL_MODEM_LOGGING_PATH,        REG_SZ     }
               ,{ DXB_REGVAL_MODEM_MAXIMUM_PORT_SPEED,  REG_DWORD  }
               ,{ DXB_REGVAL_MODEM_USER_INIT,           REG_SZ     }
            };

            int nElements = sizeof(lookup) / sizeof(lookup[0]);

            for (int i = 0; i < nElements; i++)
            {
               if (REG_BINARY == lookup[i].dwRegType)
               {
                  DWORD len = 0;
                  if ( (TRUE == RegValueExists(hChildDriverKey, lookup[i].szKeyName)) 
                      && (TRUE == RegReadBinaryValue(hChildDriverKey, lookup[i].szKeyName, NULL, len)) )
                  {
                     BYTE * data = new BYTE[len];
                     AffirmThrow(NULL != data);

                     if (TRUE == RegReadBinaryValue(hChildDriverKey, lookup[i].szKeyName, data, len))
                     {
                        RegWriteBinaryValue(hDriverKey, lookup[i].szKeyName, data, len);
                     }

                     delete data;
                     data = NULL;
                  }
               }
               else if (REG_DWORD == lookup[i].dwRegType)
               {
                  DWORD dw;
                  if ( (TRUE == RegValueExists(hChildDriverKey, lookup[i].szKeyName)) 
                      && (TRUE == RegReadDWordValue(hChildDriverKey, lookup[i].szKeyName, &dw)) )
                  {
                     RegWriteDWordValue(hDriverKey, lookup[i].szKeyName, dw);
                  }
               }
               else if (REG_SZ == lookup[i].dwRegType)
               {
                  CString cstrVal;
                  if ( (TRUE == RegValueExists(hChildDriverKey, lookup[i].szKeyName)) 
                      && (TRUE == RegReadStrValue(hChildDriverKey, lookup[i].szKeyName, cstrVal)) )
                  {
                     RegWriteStrValue(hDriverKey, lookup[i].szKeyName, cstrVal);
                  }
               }
               else
               {
                  Affirm(FALSE);
               }
            }

            // All done w/ the class key.

            RegCloseKey(hChildDriverKey);

            // Clobber the old device.

            SetupDiCallClassInstaller(DIF_REMOVE, hDeviceInfoSet, &curDeviceInfoData);

            // We only update one modem, so we can leave the loop.

            break;
         }
         catch(CAffirmException ae)
         {
            TRACE("Exception in CompleteModemClassUpdate nested try, GetLastError()=%d\n", GetLastError());

            if (INVALID_HANDLE_VALUE == hChildDriverKey)
               { RegCloseKey(hChildDriverKey); }
         }

         index++;                // Continuing on...
      }                          // while (enumerating class key)

      // Destroy the enum list.

      SetupDiDestroyDeviceInfoList(hDeviceInfoSet);

      // Close the device key.

      RegCloseKey(hDriverKey);

   }
   catch(CAffirmException ae)
   {
      TRACE("Exception in CompleteModemClassUpdate, GetLastError()=%d\n", GetLastError());

      if (INVALID_HANDLE_VALUE == hDriverKey)
         { RegCloseKey(hDriverKey); }
   }

   #endif // #if defined(ACCELEPORT_RAS_BUILD)
}

BOOL InstallPamA()
{
    CString msg;
    DWORD LastError, Try;
    BOOL ServiceExists = FALSE, ServiceStarted = FALSE;
    SERVICE_STATUS SvcStatus = {0};
    SC_HANDLE hService = NULL;

    // The Service Control Manager API is used to control the PAM agent...
    SC_HANDLE hScManager = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
    if ( hScManager == NULL )
	{
        msg.Format( "OpenSCManager failed, GetLastError 0x%x\n", GetLastError() );
        TRACE(msg);
        goto Exit;
    }

    // Stop the DigiPamA service if it exists and is already running
	hService = OpenService(hScManager, WANLINKS_PAMA_SERVICE_NAME_TEXT, SERVICE_ALL_ACCESS);
    if (NULL != hService)
    {
        // We need it to be stopped before we can copy...
        if ( !QueryServiceStatus(hService, &SvcStatus) )
        {
            goto Exit; // Error, we found the service but couldn't query it!
        }

        TRACE("[DgUpgrade] Queried the PAMA service\n");
        if (SvcStatus.dwCurrentState != SERVICE_STOPPED)
        {
            ServiceStarted = TRUE;
            if ( !ControlService(hService, SERVICE_CONTROL_STOP, &SvcStatus) )
            {
                goto Exit; // Error, we need to be able to do this!
            }

            // We'll wait up to 30 seconds
            for (Try = 0; Try < 30; Try++)
            {
                if (  QueryServiceStatus(hService, &SvcStatus) &&
                (SvcStatus.dwCurrentState == SERVICE_STOPPED) )
                {
                    TRACE("[DgUpgrade] Stopped!\n");
                    ServiceStarted = FALSE; // Same as stopped :-)
                    break;
                }
                TRACE(".");
                Sleep(1000);
            }
            TRACE("\n");
        }
    }

    if (ServiceStarted)
    {
        TRACE("[DgUpgrade] Couldn't stop existing service.\n");
        ServiceStarted = FALSE; // really means we didn't get the new service in and started
                                // because the old service couldn't be stopped
        goto Exit;
    }

    // Is the PAMA service already installed?
    if (NULL == hService)
    {
        // No, install it now
        hService = CreateService( hScManager,
                                  WANLINKS_PAMA_SERVICE_NAME_TEXT,
                                  WANLINKS_PAMA_SERVICE_DESCRIPTION,
                                  SERVICE_ALL_ACCESS,
                                  SERVICE_WIN32_OWN_PROCESS,
                                  SERVICE_AUTO_START,
                                  SERVICE_ERROR_IGNORE,
                                  WANLINKS_PAMA_BINARY_PATH_TEXT,
                                  NULL, NULL, NULL, NULL, NULL );
        if ( (hService == NULL) && (ERROR_SERVICE_EXISTS != (LastError = GetLastError())) )
        {
            TRACE( "[DgUpgrade] CreateService: Failed: GetLastError: %d\n", LastError);
            goto Exit;
        }
    }

    ServiceStarted = StartService(hService, 0, NULL);
    LastError = (!ServiceStarted) ? GetLastError() : ERROR_SUCCESS;

    if (ServiceStarted || (LastError == ERROR_SERVICE_ALREADY_RUNNING))
    {
        TRACE("%s has been succeedfully started\n", WANLINKS_PAMA_SERVICE_NAME_TEXT);
    }
    else
    {
        TRACE("%s StartService() failed!  System error: %d\n", WANLINKS_PAMA_SERVICE_NAME_TEXT, LastError);
    }

Exit:
    if ( hService != NULL )
        CloseServiceHandle( hService );
    if ( hScManager != NULL )
        CloseServiceHandle( hScManager );

    return ServiceStarted;
}

// 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)
               {
                  // Determine if this is a new installation,
                  // an upgrade from the WanLinks-inclusive
                  // driver, or an upgrade from a split driver.
                  CreatePrivateData(DeviceInfoSet, DeviceInfoData, Context);

                  // Request port-processing.
                  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)
               {
                  // Cleanup old Wanlinks driver and perform 
                  // necessary additional installation steps
                  UpdateInstallationStatus(DeviceInfoSet, 
                                           DeviceInfoData, 
                                           (PDIGI_CO_INSTALLER_PRIVATE_DATA)Context->PrivateData,
                                           ADDING);

                  // Write the adapters Friendly Name to the registry
                  #if defined(ACCELEPORT_RAS_BUILD) || defined(ACCELEPORT_XP_BUILD)
                     WriteAdapterFriendlyName(DeviceInfoSet, DeviceInfoData, MPSCLASS);
                  #elif defined(DATAFIRE_RAS_BUILD) || defined(DATAFIRE_SYNC_BUILD)
                     WriteAdapterFriendlyName(DeviceInfoSet, DeviceInfoData, NETCLASS);
                  #else
                     ASSERT(FALSE);
                  #endif

                  // Copy the inf files for our client drivers...
                  // (lnb: I wonder if this fcn'ality is superceded by cocpyinf.dll.)
                  dwResult = PostProcessInstallDevice(DeviceInfoSet, DeviceInfoData);

                  // Install PAM agent DLL
                  InstallPamA();
                  
                  // Handle the private data we recorded while 
                  // handling the first DIF_INSTALLDEVICE msg.
                  PostProcessPrivateData(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

         // Read a Registry key written when we received the
         // last DIF_INSTALLDEVICE msg. to determine whether
         // or not this is a silent installation
         HKEY hParamsKey;
         AffirmThrow(OpenAdapterParamsKey(DeviceInfoSet, DeviceInfoData, &hParamsKey));
         AffirmThrow(RegReadBoolValue(hParamsKey, WANLINKS_REGKEY_SILENT_INSTALL, &fSilent, FALSE));
         RegCloseKey(hParamsKey);

         // 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(DeviceInfoSet, DeviceInfoData, NULL, 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)
            {
               // If this port is being updated from an old
               // Wanlinks driver installation, we need to
               // prepare its PortName for the Ports Class
               // Installer, otherwise it'll get assigned a 
               // new COM number.
               PreparePortNameForClassInstaller(DeviceInfoSet, 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
                  WriteDeviceFriendlyName(DeviceInfoSet, DeviceInfoData, strInstance, strDxbPath);
               }
            }

            dwResult = Context->InstallResult;
         }
      }
      break;

      case DIF_REMOVE:
      {
         TRACE("DigiDxbClientCoInstaller: DIF_REMOVE\n");      

         // Remove this device's Serialcomm key
         CleanupRegistrySerialcommKey(DeviceInfoSet, DeviceInfoData);

         dwResult = Context->InstallResult;
      }
      break;

      default:
      break;
      }
   }

   catch(CAffirmException ae)
   {
      dwResult = ERROR_DI_DONT_INSTALL;
   }

   return dwResult;
}

// The Modem class coinstaller when updating from the old 
// Wanlinks driver to the new AccelePort RAS split driver.
extern "C" DWORD PASCAL DigiDxbModemClassCoInstaller(
   DI_FUNCTION               InstallFunction,
   HDEVINFO                  DeviceInfoSet,
   PSP_DEVINFO_DATA          DeviceInfoData,  
   PCOINSTALLER_CONTEXT_DATA Context)
{
   AFX_MANAGE_STATE(AfxGetStaticModuleState());

   PrintInstallFunction("DigiDxbModemClassCoInstaller", InstallFunction);

   DWORD dwResult = NO_ERROR;

   try
   {
      switch(InstallFunction)
      {
         case DIF_PROPERTYCHANGE:
         {
            dwResult = Context->InstallResult;
            break;
         }
      
         case DIF_INSTALLDEVICE:
         {
            dwResult = Context->InstallResult;

            // If 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)
                  && (Context->InstallResult == NO_ERROR) )
               {
                  CompleteModemClassUpdate(DeviceInfoSet, DeviceInfoData);
               }
            }
         break;
         }

         case DIF_REMOVE:
         {
            dwResult = Context->InstallResult;
            break;
         }

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

   return dwResult;
}

// CreatePrivateData creates the private data structure
// associated with DIF_INSTALLDEVICE messages and 
// ERROR_DI_POSTPROCESSING_REQUIRED.
void CreatePrivateData(
   HDEVINFO                   DeviceInfoSet,
   PSP_DEVINFO_DATA           DeviceInfoData,  
   PCOINSTALLER_CONTEXT_DATA  Context)
{
   // Perhaps this is rather klugey? Here's the deal:
   // We need to distinguish between a new installation
   // and an upgrade. Furthermore, we need to distinguish
   // between upgrading from the WanLinks-inclusinve L3P 
   // driver and upgrading from a "split driver" (i.e., the 
   // DataFire SYNC-only driver).
   //
   // 1. If we are upgrading, we want to do a silent install.
   //    Otherwise, we want to display the Digi Config Wizard.
   //
   // 2. If we are upgrading from a "split driver", we don't 
   //    want to increment the HKLM\SOFTWARE\Digi\...\REFCOUNT 
   //    key. 
   //    If we are upgrading from the WanLinks-inclusive driver,
   //    we need to decrement the HKLM\SOFTWARE\Digi\WanLinks\
   //    REFCOUNT key and to increment the HKLM\SOFTWARE\Digi\
   //    DataFire_SYNC\REFCOUNT key, for instance.
   //    If this is a new install, we just need to increment the
   //    HKLM\SOFTWARE\Digi\DataFire_SYNC\REFCOUNT key, for 
   //    instance.
   //
   // There are a number of ways to distinguish between an upgrade 
   // and a new installation:
   //
   // a. The DeviceDesc key is "PCI Device" for a new installation
   //    and the FriendlyName key does not exist. These will both
   //    be created/modified during installation.
   //
   // b. We could create a key during post-processing to indicate
   //    that the device is installed, such that if the key doesn't
   //    yet exist, we can assume this is a new installation, and
   //    if the key does exist, we can assume this is an upgrade.
   //
   // I (lnb) choose to check whether or not the FriendlyName
   // exists.
   //
   // Then, to distinguish between upgrading from the WanLinks-
   // inclusive driver and a split driver:
   // 
   // i. The Service key is "DIGIDXB" if upgrading from WanLinks-
   //    inclusive driver.
   //
   // We choose to use the PrivateData member of the 
   // PCOINSTALLER_CONTEXT_DATA structure to maintain this
   // information.

   try
   {
      Context->PrivateData = NULL;

      PDIGI_CO_INSTALLER_PRIVATE_DATA pMyPrivates = new DIGI_CO_INSTALLER_PRIVATE_DATA;
      AffirmThrow(NULL != pMyPrivates);

      // Initialize the strucutre and point the Context data to it.

      pMyPrivates->m_bDoUpdateDriver = FALSE;
      pMyPrivates->m_bUpgradingFromWanlinks = FALSE;
      pMyPrivates->m_pcsFriendlyName = NULL;

      Context->PrivateData = (PVOID)pMyPrivates;

      // Query the FriendlyName.

      BOOL bResult;
      TCHAR szRegBuf[MAX_PATH];
      DWORD dwRegDataType;

      bResult = 
         SetupDiGetDeviceRegistryProperty(
            DeviceInfoSet,
            DeviceInfoData,
            SPDRP_FRIENDLYNAME,
            &dwRegDataType,
            (PBYTE)&szRegBuf[0],
            sizeof(szRegBuf),
            NULL);

      // If the FriendlyName already exists, this is an upgrade.

      if (TRUE == bResult)
      {
         pMyPrivates->m_bDoUpdateDriver = TRUE;

         // Remember the friendly name, used later if we are
         // updating the old Wanlinks driver to the AccelePort
         // RAS split driver.

         CString *szInstance = new CString(szRegBuf);
         if (NULL != szInstance)
         {
            pMyPrivates->m_pcsFriendlyName = szInstance;
         }

         // Read the Service name key.

         bResult = SetupDiGetDeviceRegistryProperty(DeviceInfoSet,
                                                    DeviceInfoData,
                                                    SPDRP_SERVICE,
                                                    NULL,
                                                    (PBYTE)&szRegBuf[0],
                                                    sizeof(szRegBuf),
                                                    NULL);

         // If the Service key references the old Wanlinks Dxb driver
         // service, this is an update from the old Wanlinks driver.
         
         if ( (TRUE == bResult)
            && (0 == _tcsicmp(szRegBuf, WANLINKS_ORIGINAL_DXB_NAME_TEXT)) )
         {
            pMyPrivates->m_bUpgradingFromWanlinks = TRUE;
         }
      }
   }
   catch(CAffirmException ae)
   {
      DWORD lastError = GetLastError();
      TRACE("Exception in CreatePrivateData, GetLastError()=%d\n", lastError);
   }
}

// PostProcessPrivateData records state information
// created by CreatePrivateData to the Registry so 
// that we can use the information when we get a
// DIF_NEWDEVICEWIZARD_FINISHINSTALL message.
void PostProcessPrivateData(
   HDEVINFO                   DeviceInfoSet,
   PSP_DEVINFO_DATA           DeviceInfoData,  
   PCOINSTALLER_CONTEXT_DATA  Context)
{

   // Unfortunately, our PrivateData is not accessible when we next
   // receive the DIF_NEWDEVICEWIZARD_FINISHINSTALL msg., so we need
   // to write a value to the Registry to communicate to ourselves
   // whether or not we should perform a silent install. We also 
   // need to clean up memory.

   PDIGI_CO_INSTALLER_PRIVATE_DATA pMyPrivates = 
      (PDIGI_CO_INSTALLER_PRIVATE_DATA)Context->PrivateData;

   if (NULL != pMyPrivates)
   {
      HKEY hParamsKey;

      if (TRUE == OpenAdapterParamsKey(DeviceInfoSet, DeviceInfoData, &hParamsKey))
      {
         RegWriteBoolValue(hParamsKey, WANLINKS_REGKEY_SILENT_INSTALL, pMyPrivates->m_bDoUpdateDriver);
         RegCloseKey(hParamsKey);
      }

      if (NULL != pMyPrivates->m_pcsFriendlyName)
      {
         delete pMyPrivates->m_pcsFriendlyName;
         pMyPrivates->m_pcsFriendlyName = NULL;
      }

      delete pMyPrivates;
      pMyPrivates = NULL;
      Context->PrivateData = NULL;
   }
}

#else // #ifdef NT5

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

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

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

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

#endif
