/*****************************************************************************
* Copyright (c) 1990-98 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
*
*****************************************************************************/


#include "stdafx.h"
#include "Update.h"
#include "ObjectFactory.h"
#include "Version.h"
#include "dxbbuildnumber.h"

#define DXB_REGKEY_NETRULES                     TEXT("NetRules")
#define DXB_REGKEY_LINKAGE                      TEXT("Linkage")
#define DXB_REGKEY_UPDATES                      TEXT("Updates")

#define DXB_REGKEY_SOFTWARE_WANLINKS_NETRULES   DXB_REGKEY_SOFTWARE_CURRENTVERSION TEXT("\\") DXB_REGKEY_NETRULES
#define DXB_REGKEY_SOFTWARE_UPDATES             DXB_REGKEY_SOFTWARE_CURRENTVERSION TEXT("\\") DXB_REGKEY_UPDATES

#define DXB_REGVAL_SOFTWAREVERSION              TEXT("SoftwareVersion")
#define DXB_REGVAL_PRODUCTVERSION               TEXT("ProductVersion")
#define DXB_REGVAL_MAJORVERSION                 TEXT("MajorVersion")
#define DXB_REGVAL_MINORVERSION                 TEXT("MinorVersion")
#define DXB_REGVAL_BUILDNUMBER                  TEXT("BuildNumber")
#define DXB_REGVAL_INFOPTION                    TEXT("InfOption")
#define DXB_REGVAL_INFNAME                      TEXT("InfName")

#define DXB_REGKEY_NETWORKCARDS                 TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards")

#define DXB_REGVAL_MANUFACTURER                 TEXT("Manufacturer")
#define DXB_REGVAL_PRODUCTNAME                  TEXT("ProductName")

#define DXB_REGKEY_CURRENTCONTROLSET            TEXT("SYSTEM\\CurrentControlSet")
#define DXB_REGKEY_CONTROL                      TEXT("Control")
#define DXB_REGKEY_CCS_CONTROL                  DXB_REGKEY_CURRENTCONTROLSET TEXT("\\") DXB_REGKEY_CONTROL
#define DXB_REGKEY_SERVICES                     TEXT("Services")
#define DXB_REGKEY_CCS_SERVICES                 DXB_REGKEY_CURRENTCONTROLSET TEXT("\\") DXB_REGKEY_SERVICES

#define DXB_REGKEY_CCS_SERVICEGROUPORDER        DXB_REGKEY_CCS_CONTROL TEXT("\\") TEXT("ServiceGroupOrder")
#define DXB_REGVAL_LIST                         TEXT("List")
#define DXB_REGVAL_GROUP                        TEXT("Group")
#define DXB_REGVAL_DEPENDONGROUP                TEXT("DependOnGroup")
#define DXB_REGVAL_DEPENDONSERVICE              TEXT("DependOnService")
#define DXB_REGVAL_OTHERDEPENDENCIES            TEXT("OtherDependencies")
#define DXB_REGVAL_ROUTE                        TEXT("Route")
#define DXB_REGVAL_PARAMETERS                   TEXT("Parameters")
#define DXB_REGVAL_WANX                         TEXT("WANX")
#define DXB_REGVAL_WANX_DRIVER                  TEXT("DigiWanX")
#define DXB_REGVAL_SNID                         TEXT("SNID")
#define DXB_REGVAL_NDIS                         TEXT("NDIS")

#define DXB_REGVAL_EMPTY_MULTI_SZ               TEXT("\0")

#define MIN_SNID                                TEXT("1")
#define MAX_SNID                                TEXT("4")

// Ok, these next two functions key off the fact that all WAN Links software
// have a SoftwareVersion value in their HKLM\Digi\????\CurrentVersion key.
//
// I realize that this is NOT the most elegant method of detecting whether or not
// the software key belongs to the WAN Links package, but after you have shipped
// a version, then need to go back and do some cleanup, what's a guy gonna do?
//
// double check is done of the software name against a list of known WAN Links
// software is needed.

static BOOL GetCurrentVersion(CString const & strSoftware, CVersion & ver)
{
   BOOL fSuccess = FALSE;

   try
   {
      CString strSoftwareReg;
      strSoftwareReg.Format(TEXT("%s\\%s\\%s"), DXB_REGKEY_SOFTWARE_DIGI, strSoftware, DXB_REGKEY_CURRENTVERSION);

      HKEY hKeySoftware = NULL;
      if (RegOpenKey(HKEY_LOCAL_MACHINE, strSoftwareReg, &hKeySoftware))
      {
         if (RegValueExists(hKeySoftware, DXB_REGVAL_SOFTWAREVERSION))
         {
            try
            {
               CString strVersion;
               AffirmThrow(RegReadStrValue(hKeySoftware, DXB_REGVAL_SOFTWAREVERSION, strVersion));
               ver = strVersion;
               fSuccess = TRUE;
            }
            catch (CAffirmException ae)
            {
               TRACE("An error occured while reading %s's software version!\n", strSoftware);
            }
         }

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

   return fSuccess;
}

static BOOL IsWanLinksSoftware(CString const & strSoftware)
{
   CVersion ver;
   BOOL fIsWanLinksSoftware = GetCurrentVersion(strSoftware, ver);

#if defined(_DEBUG)
   // Double check
   if (fIsWanLinksSoftware)
   {
      static TCHAR const * const apszKnownWanLinksSoftware[] =
      {
         WANLINKS_PROJECT_NAME_TEXT,
         WANLINKS_DXB_NAME_TEXT,
         WANLINKS_ISDN_NAME_TEXT,
         WANLINKS_LAN_NAME_TEXT,
         WANLINKS_PCI_NAME_TEXT,
         WANLINKS_PPP_NAME_TEXT,
         WANLINKS_SNA_NAME_TEXT,
         WANLINKS_WANX_NAME_TEXT,
         WANLINKS_570MP_NAME_TEXT,
         WANLINKS_SOH_NAME_TEXT,
         WANLINKS_PAMA_SERVICE_NAME_TEXT
      };

      BOOL fSoftwareIsInTheList = FALSE;

      for (int i = 0; i < ElementCount(apszKnownWanLinksSoftware); i++)
      {
         if (strSoftware == apszKnownWanLinksSoftware[i])
         {
            fSoftwareIsInTheList = TRUE;
            break;
         }
      }

      if (!fSoftwareIsInTheList) TRACE("\n\nWARNING: %s seems to be WAN Links software, but isn't in our known list!\n\n", strSoftware);
      // Affirm(fSoftwareIsInTheList);
   }
#endif

   return fIsWanLinksSoftware;
}

// This function is used to cruise through all of the software keys under
// HKLM\Software\Digi and update ALL of the version values to whatever is
// passed in.  The list of version values (SoftwareVersion, ProductVersion,
// MajorVersion, MinorVersion, & BuildNumber) may be overkill, but better
// to be safe than sorry.
static BOOL SetAllInstalledVersions(CVersion const & verNew)
{
   BOOL fSuccess = FALSE;

   try
   {
      HKEY hKeyDigiSoftware = NULL;
      AffirmThrow(RegOpenKey(HKEY_LOCAL_MACHINE, DXB_REGKEY_SOFTWARE_DIGI, &hKeyDigiSoftware));

      try
      {
         CStringArray astrKeys;
         AffirmThrow(RegEnumSubKeyNames(hKeyDigiSoftware, astrKeys));

         for (int i = 0; i < astrKeys.GetSize(); i++)
         {
            if (IsWanLinksSoftware(astrKeys[i]))
            {
               CVersion verOld;
               AffirmThrow(GetCurrentVersion(astrKeys[i], verOld));

               if (verOld < verNew)
               {
                  CString strSoftwareReg;
                  strSoftwareReg.Format(TEXT("%s\\%s"), astrKeys[i], DXB_REGKEY_CURRENTVERSION);

                  HKEY hKeySoftware = NULL;
                  AffirmThrow(RegOpenKey(hKeyDigiSoftware, strSoftwareReg, &hKeySoftware));

                  try
                  {
                     TRACE("Updating %s's version from %s to %s\n", astrKeys[i], (LPCTSTR)verOld, (LPCTSTR)verNew);

                     AffirmThrow(RegWriteStrValue(hKeySoftware, DXB_REGVAL_SOFTWAREVERSION, (LPCTSTR)verNew));
                     AffirmThrow(RegWriteStrValue(hKeySoftware, DXB_REGVAL_PRODUCTVERSION, (LPCTSTR)verNew));

                     // I should have thought of a better way to do the following
                     // Maybe add MAJORVERSION(), MINORVERSION(), & BUILDNUMBER()
                     // cast operators to the CVersion method.
                     CString strComponent;

                     strComponent.Format("%d", verNew.m_nMajor);
                     AffirmThrow(RegWriteStrValue(hKeySoftware, DXB_REGVAL_MAJORVERSION, strComponent));

                     strComponent.Format("%d", verNew.m_nMinor);
                     AffirmThrow(RegWriteStrValue(hKeySoftware, DXB_REGVAL_MINORVERSION, strComponent));

                     strComponent.Format("%d", verNew.m_nBuild);
                     AffirmThrow(RegWriteStrValue(hKeySoftware, DXB_REGVAL_BUILDNUMBER, strComponent));
                  }
                  catch (CAffirmException ae)
                  {
                     TRACE("An error occured while updating installed component versions!\n");
                  }

                  RegCloseKey(hKeySoftware);
               }
               else
               {
                  TRACE("%s has a newer sofware version: %s!\n", astrKeys[i], (LPCTSTR)verOld);
               }
            }
         }

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

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

   return fSuccess;
}

static BOOL WhackOldUpdateLogs(void)
{
   TRACE("Deleting old update logs, %s\n", DXB_REGKEY_SOFTWARE_UPDATES);
   RegDelKey(HKEY_LOCAL_MACHINE, DXB_REGKEY_SOFTWARE_UPDATES);
   return TRUE;
}

// In all cases, the very first time WAN Links (which currently includes Houston, London,
// Chaska, & Clydesdale adapters) is installed, the InfName is recorded under the
// HKLM\Software\Digi\WAN Links\CurrentVersion\NetRules key.  This is the master INF, and
// the only one which gets updated during the file copy.
static BOOL GetFirstInfName(CString & strFirstInfName)
{
   BOOL fSuccess = FALSE;

   try
   {
      HKEY hKeyWanLinksNetRules = NULL;
      AffirmThrow(RegOpenKey(HKEY_LOCAL_MACHINE, DXB_REGKEY_SOFTWARE_WANLINKS_NETRULES, &hKeyWanLinksNetRules));

      try
      {
         AffirmThrow(RegReadStrValue(hKeyWanLinksNetRules, DXB_REGVAL_INFNAME, strFirstInfName));
         TRACE("First inf name is %s\n", strFirstInfName);
         fSuccess = TRUE;
      }
      catch (CAffirmException ae)
      {
         TRACE("An error occured while reading the inf name!\n");
      }

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

   return fSuccess;
}

static BOOL FixSoftwareInfNames(CString const & strFirstInfName)
{
   BOOL fSuccess = FALSE;

   try
   {
      HKEY hKeyDigiSoftware = NULL;
      AffirmThrow(RegOpenKey(HKEY_LOCAL_MACHINE, DXB_REGKEY_SOFTWARE_DIGI, &hKeyDigiSoftware));

      try
      {
         CStringArray astrKeys;
         AffirmThrow(RegEnumSubKeyNames(hKeyDigiSoftware, astrKeys));

         for (int i = 0; i < astrKeys.GetSize(); i++)
         {
            if (IsWanLinksSoftware(astrKeys[i]))
            {
               CString strNetRules;
               strNetRules.Format(TEXT("%s\\%s\\%s"), astrKeys[i], DXB_REGKEY_CURRENTVERSION, DXB_REGKEY_NETRULES);

               HKEY hKeyNetRules = NULL;
               AffirmThrow(RegOpenKey(hKeyDigiSoftware, strNetRules, &hKeyNetRules));

               try
               {
                  CString strOldInfName;
                  AffirmThrow(RegReadStrValue(hKeyNetRules, DXB_REGVAL_INFNAME, strOldInfName));

                  if (strOldInfName != strFirstInfName)
                  {
                     TRACE("Changing %s's inf name from %s to %s\n", astrKeys[i], strOldInfName, strFirstInfName);
                     AffirmThrow(RegWriteStrValue(hKeyNetRules, DXB_REGVAL_INFNAME, strFirstInfName));
                  }
               }
               catch (CAffirmException ae)
               {
                  TRACE("An error occured while fixing software inf names!\n");
               }

               RegCloseKey(hKeyNetRules);
            }
         }

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

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

   return fSuccess;
}

static BOOL CheckNetworkCardInfName(CString const & strNetworkCard)
{
   BOOL fNeedsInfNameFixed = FALSE;

   try
   {
      CString strNetRules;
      strNetRules.Format(TEXT("%s\\%s\\%s"), DXB_REGKEY_NETWORKCARDS, strNetworkCard, DXB_REGKEY_NETRULES);

      HKEY hKeyNetRules = NULL;
      AffirmThrow(RegOpenKey(HKEY_LOCAL_MACHINE, strNetRules, &hKeyNetRules));

      try
      {
         CString strInfOption;
         AffirmThrow(RegReadStrValue(hKeyNetRules, DXB_REGVAL_INFOPTION, strInfOption));

         // NOTE: Only the following adapter types MIGHT need to have their
         //       inf names changed.  The bug should have been fixed with
         //       newer adapters.
         static TCHAR const * const apszOptionsThatNeedInfNameFix[] =
         {
            DXB_REGVAL_OPTION_ENUM_HOUSTON,
            DXB_REGVAL_OPTION_ENUM_LONDON,
            DXB_REGVAL_OPTION_ENUM_CHASKA,
            DXB_REGVAL_OPTION_ENUM_CLYDE,
         };

         for (int i = 0; i < ElementCount(apszOptionsThatNeedInfNameFix); i++)
         {
            if (strInfOption == apszOptionsThatNeedInfNameFix[i])
            {
               fNeedsInfNameFixed = TRUE;
               break;
            }
         }
      }
      catch (CAffirmException ae)
      {
         TRACE("An error occured while checking network card inf names!\n");
      }

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

   return fNeedsInfNameFixed;
}

static BOOL FixNetworkCardsInfNames(CString const & strFirstInfName)
{
   BOOL fSuccess = FALSE;

   try
   {
      HKEY hKeyNetworkCards = NULL;
      AffirmThrow(RegOpenKey(HKEY_LOCAL_MACHINE, DXB_REGKEY_NETWORKCARDS, &hKeyNetworkCards));

      try
      {
         CStringArray astrKeys;
         AffirmThrow(RegEnumSubKeyNames(hKeyNetworkCards, astrKeys));

         for (int i = 0; i < astrKeys.GetSize(); i++)
         {
            if (CheckNetworkCardInfName(astrKeys[i]))
            {
               CString strNetRules;
               strNetRules.Format(TEXT("%s\\%s"), astrKeys[i], DXB_REGKEY_NETRULES);

               HKEY hKeyNetRules = NULL;
               if (RegOpenKey(hKeyNetworkCards, strNetRules, &hKeyNetRules))
               {
                  try
                  {
                     CString strOldInfName;
                     AffirmThrow(RegReadStrValue(hKeyNetRules, DXB_REGVAL_INFNAME, strOldInfName));

                     if (strOldInfName != strFirstInfName)
                     {
                        TRACE("Changing network card %s's inf name from %s to %s\n", astrKeys[i], strOldInfName, strFirstInfName);
                        AffirmThrow(RegWriteStrValue(hKeyNetRules, DXB_REGVAL_INFNAME, strFirstInfName));

                        // whack the old inf file
                        TCHAR szSysDir[MAX_PATH];
                        GetSystemDirectory(szSysDir, ElementCount(szSysDir));

                        CString strOldInfPath;
                        strOldInfPath.Format(TEXT("%s\\%s"), szSysDir, strOldInfName);

                        TRACE("Deleting %s\n", strOldInfPath);
                        DeleteFile(strOldInfPath);
                     }
                  }
                  catch (CAffirmException ae)
                  {
                     TRACE("An error occured while fixing network card inf names!\n");
                  }

                  RegCloseKey(hKeyNetRules);
               }
            }
         }

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

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

   return fSuccess;
}

static BOOL FixInfNames(void)
{
   BOOL fSuccess = FALSE;

   try
   {
      CString strFirstInfName;
      AffirmThrow(GetFirstInfName(strFirstInfName));

      AffirmThrow(FixSoftwareInfNames(strFirstInfName));
      AffirmThrow(FixNetworkCardsInfNames(strFirstInfName));

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

   return fSuccess;
}

static BOOL NetworkCardValuesMatch(CString const & strCard, CString const & strManufacturer, CString const & strProductName)
{
   BOOL fMatch = FALSE;

   try
   {
      CString strNetworkCard;
      strNetworkCard.Format(TEXT("%s\\%s"), DXB_REGKEY_NETWORKCARDS, strCard);

      HKEY hKeyNetworkCard = NULL;
      AffirmThrow(RegOpenKey(HKEY_LOCAL_MACHINE, strNetworkCard, &hKeyNetworkCard));

      try
      {
         CString strCardManufacturer;
         AffirmThrow(RegReadStrValue(hKeyNetworkCard, DXB_REGVAL_MANUFACTURER, strCardManufacturer));

         if (strCardManufacturer == strManufacturer)
         {
            // passed test 1
            CString strCardProductName;
            AffirmThrow(RegReadStrValue(hKeyNetworkCard, DXB_REGVAL_PRODUCTNAME, strCardProductName));
            fMatch = (strCardProductName == strProductName);
         }
      }
      catch (CAffirmException ae)
      {
         TRACE("An error occured while matching network card values!\n");
      }

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

   return fMatch;
}

static BOOL IsWanLinksNetworkCard(CString const & strCard)
{
   BOOL fIsWanLinksNetworkCard = FALSE;

   try
   {
      // NOTE: Maybe change the following to a data table and for loop if product name list grows.
      fIsWanLinksNetworkCard = (NetworkCardValuesMatch(strCard, DXB_REGKEY_DIGI, WANLINKS_DXB_NAME_TEXT) ||
                                NetworkCardValuesMatch(strCard, DXB_REGKEY_DIGI, WANLINKS_570MP_NAME_TEXT));
   }
   catch(CAffirmException ae)
   {
   }

   return fIsWanLinksNetworkCard;
}

static BOOL FixServiceGroupOrder(void)
{
   BOOL fSuccess = FALSE;

   try
   {
      HKEY hKeySrvGrpOrder = NULL;
      AffirmThrow(RegOpenKey(HKEY_LOCAL_MACHINE, DXB_REGKEY_CCS_SERVICEGROUPORDER, &hKeySrvGrpOrder));

      try
      {
         CStringArray astrList;
         AffirmThrow(RegReadMultiStrValue(hKeySrvGrpOrder, DXB_REGVAL_LIST, astrList));

         for (int i = 0; i < astrList.GetSize(); i++)
         {
            if ((astrList[i] != WANLINKS_SERVICE_GROUP_TEXT) && (astrList[i] != DXB_REGVAL_NDIS))
               continue;   // not what we're lookin' for, so check the next one

            if (astrList[i] == DXB_REGVAL_NDIS)
            {
               TRACE("Inserting %s into service group order list at position %d\n", WANLINKS_SERVICE_GROUP_TEXT, i);
               astrList.InsertAt(i, WANLINKS_SERVICE_GROUP_TEXT);
               AffirmThrow(RegWriteMultiStrValue(hKeySrvGrpOrder, DXB_REGVAL_LIST, astrList));
            }
            // else DIGIDS is already in the list

            break;
         }

         fSuccess = TRUE;
      }
      catch(CAffirmException ae)
      {
         TRACE("An error occured while fixing service group ordering!\n");
      }

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

   return fSuccess;
}

static BOOL FixGroupInfo(void)
{
   BOOL fSuccess = FALSE;

   try
   {
      HKEY hKey = NULL;

      try
      {
         // 1st fix up DigiDxb's group entry - it is a stream provider, so must be in the DIGIDS group.
         // DigiDxb's group/depend entries should look like:
         //    Group = "DIGIDS"
         //    DependOnService = "" - this should be ok in previous installs
         //    DependOnGroup = "" - this should be ok in previous installs
         //    Linkage
         //       OtherDependencies = "" - this should be ok in previous installs
         if (RegOpenKey(HKEY_LOCAL_MACHINE, DXB_REGKEY_CCS_SERVICES TEXT("\\") WANLINKS_DXB_NAME_TEXT, &hKey))
         {
            CString strGroup;
            Affirm(RegReadStrValue(hKey, DXB_REGVAL_GROUP, strGroup));
            TRACE("DigiDxb Group = %s\n", strGroup);
            if (strGroup != WANLINKS_SERVICE_GROUP_TEXT)
            {
               TRACE("Setting DigiDxb's %s value to %s\n", DXB_REGVAL_GROUP, WANLINKS_SERVICE_GROUP_TEXT);
               Affirm(RegWriteStrValue(hKey, DXB_REGVAL_GROUP, WANLINKS_SERVICE_GROUP_TEXT));
            }

            RegCloseKey(hKey);
            hKey = NULL;
         }

         // Next fix up the following services to depend on the DIGIDS group, and not some other service.
         // These are all consumers of the stream provider.  Their registry should look like:
         //    Group = "????" - however it was installed, usually "NDIS", "BASE", or "EXTENDED BASE"
         //    DependOnService = ""
         //    DependOnGroup = "DIGIDS"
         //    Linkage
         //       OtherDependencies = "+DIGIDS" - adding this will cause the above to happen after bindings
         static TCHAR const * const apszServiceLinkages[] = 
         {
            { DXB_REGKEY_CCS_SERVICES TEXT("\\") WANLINKS_ASYN_NAME_TEXT TEXT("\\") DXB_REGKEY_LINKAGE },
            { DXB_REGKEY_CCS_SERVICES TEXT("\\") WANLINKS_ISDN_NAME_TEXT TEXT("\\") DXB_REGKEY_LINKAGE },
            { DXB_REGKEY_CCS_SERVICES TEXT("\\") WANLINKS_LAN_NAME_TEXT  TEXT("\\") DXB_REGKEY_LINKAGE },
            { DXB_REGKEY_CCS_SERVICES TEXT("\\") WANLINKS_PPP_NAME_TEXT  TEXT("\\") DXB_REGKEY_LINKAGE },
            { DXB_REGKEY_CCS_SERVICES TEXT("\\") WANLINKS_SNA_NAME_TEXT  TEXT("\\") DXB_REGKEY_LINKAGE },
            { DXB_REGKEY_CCS_SERVICES TEXT("\\") WANLINKS_WANX_NAME_TEXT TEXT("\\") DXB_REGKEY_LINKAGE }
         };

         for (int i = 0; i < ElementCount(apszServiceLinkages); i++)
         {
            if (RegOpenKey(HKEY_LOCAL_MACHINE, apszServiceLinkages[i], &hKey))
            {
               // NOTE: The "+" prefix below.  This is needed to indicate that the other dependency is a group rather than a service.
               //       See help on CreateService() and/or SC_GROUP_IDENTIFIER for more details.
#define DXB_REGVAL_GROUP_ID_DIGIDS_MULTI_SZ     TEXT("+") WANLINKS_SERVICE_GROUP_TEXT DXB_REGVAL_EMPTY_MULTI_SZ

               CStringArray astrOtherDeps;
               Affirm(RegReadMultiStrValue(hKey, DXB_REGVAL_OTHERDEPENDENCIES, astrOtherDeps));
               if ((astrOtherDeps.GetSize() != 1) || (astrOtherDeps[0] != DXB_REGVAL_GROUP_ID_DIGIDS_MULTI_SZ))
               {
                  TRACE("Setting %s\\%s = %s\n", apszServiceLinkages[i], DXB_REGVAL_OTHERDEPENDENCIES, WANLINKS_SERVICE_GROUP_TEXT);
                  Affirm(RegWriteMultiSzValue(hKey, DXB_REGVAL_OTHERDEPENDENCIES, DXB_REGVAL_GROUP_ID_DIGIDS_MULTI_SZ));
               }

               RegCloseKey(hKey);
               hKey = NULL;
            }
         }

         fSuccess = TRUE;
      }
      catch (CAffirmException ae)
      {
         TRACE("An error occured while fixing group information!\n");
      }

      if (hKey != NULL) RegCloseKey(hKey);
   }
   catch(CAffirmException ae)
   {
   }

   return fSuccess;
}

// NOTE: This a recursive function
static BOOL DuplicateAdapterToDevice(HKEY const hKeyParent, CString const & strSubKey)
{
   BOOL fSuccess = FALSE;

   TRACE("Checking %s for adapter device duplication\n", strSubKey);

   try
   {
      AffirmThrow(hKeyParent != NULL);

      HKEY hKey = NULL;
      AffirmThrow(RegOpenKey(hKeyParent, strSubKey, &hKey));

      try
      {
         if (RegValueExists(hKey, DXB_REGVAL_ADAPTER) && !RegValueExists(hKey, DXB_REGVAL_DEVICE))
         {
            // Duplicate the adapter value first, since this really is the recursion stopper
            CString strAdapter;
            AffirmThrow(RegReadStrValue(hKey, DXB_REGVAL_ADAPTER, strAdapter));

            TRACE("Duplicating Adapter = %s to Device in %s\n", strAdapter, strSubKey);
            AffirmThrow(RegWriteStrValue(hKey, DXB_REGVAL_DEVICE, strAdapter));

            // Next follow the ServiceName value, if there is one
            // NOTE: It is very important that this be inside the if statement to avoid
            //       getting into an endless loop examining DigiDxb? keys
            if (RegValueExists(hKey, DXB_REGVAL_SERVICENAME))
            {
               CString strServiceName;
               AffirmThrow(RegReadStrValue(hKey, DXB_REGVAL_SERVICENAME, strServiceName));

               CString strPath;
               strPath.Format(TEXT("%s\\%s"), DXB_REGKEY_CCS_SERVICES, strServiceName);

               AffirmThrow(DuplicateAdapterToDevice(HKEY_LOCAL_MACHINE, strPath));
            }
         }

         // Look at subkeys
         CStringArray astrSubKeys;
         AffirmThrow(RegEnumSubKeyNames(hKey, astrSubKeys));
         for (int i = 0; i < astrSubKeys.GetSize(); i++)
         {
            AffirmThrow(DuplicateAdapterToDevice(hKey, astrSubKeys[i]));
         }

         fSuccess = TRUE;
      }
      catch(CAffirmException ae)
      {
         TRACE("An error occured while duplicating adapter to device!\n");
      }

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

   return fSuccess;
}

static void ConvertSnid(CString const & strInSnid, CString & strOutSnid)
{

	// Convert the SNID (in old releases the SNID is a decimal number)
	//                  (in the new release it is a string)
	//
	//  If it is one byte long it is a DXB Snid, otherwise, it is a SOH SNID.
	//
	if (strInSnid.GetLength() == 1)
	{
		// Is it already an ASCII String.  If not it needs to be converted
		if (strInSnid < MIN_SNID || strInSnid > MAX_SNID)
		{
			// Convert to a String
			strOutSnid.Format("%d",(int) strInSnid[0]);
		}
		else
		{
			// Already converted, so leave it as is.
			strOutSnid = strInSnid;
		}

	}
	else
	{
		// It is a SOH Snid so, it is already an ASCII String.
		strOutSnid = strInSnid;
	}

}

static BOOL ConvertAdapterWanxSnids(HKEY const hKeyParent, CString const & strSubKey)
{
   BOOL fSuccess	= FALSE;
	HKEY hKey		= NULL;

   TRACE("Converting %s Xwan Snids\n", strSubKey);

   try
   {
      AffirmThrow(hKeyParent != NULL);

		try
      {

			RegOpenKey(hKeyParent, strSubKey, &hKey);

			if (hKey)
			{
				CStringArray astrSnid;
 				CStringArray astrNewSnid;

            AffirmThrow(RegReadMultiStrValue(hKey, DXB_REGVAL_SNID, astrSnid));

				for (int i = 0; i < astrSnid.GetSize(); i++)
				{
					CString strSnid;	

					// Convert the SNID
					ConvertSnid(astrSnid[i],strSnid);
               astrNewSnid.InsertAt(i, strSnid);

				}

				AffirmThrow(RegWriteMultiStrValue(hKey, DXB_REGVAL_SNID, astrNewSnid));
			}

         fSuccess = TRUE;
      }
      catch(CAffirmException ae)
      {
         TRACE("An error occured while updating the XWAN SNID!\n");
      }

		if (hKey)
		{
			RegCloseKey(hKey);
		}
   }
   catch(CAffirmException ae)
   {
   }

   return fSuccess;
}

static BOOL FixAdapterValues(void)
{
   BOOL fSuccess = FALSE;
	HKEY hKey		= NULL;

   try
   {
      HKEY hKeyDigiDxbLinkage = NULL;
		if (RegOpenKey(HKEY_LOCAL_MACHINE, DXB_REGKEY_CCS_SERVICES TEXT("\\") WANLINKS_DXB_NAME_TEXT TEXT("\\") DXB_REGKEY_LINKAGE, &hKeyDigiDxbLinkage))
      {
         CStringArray astrRoute;
         Affirm(RegReadMultiStrValue(hKeyDigiDxbLinkage, DXB_REGVAL_ROUTE, astrRoute));
         RegCloseKey(hKeyDigiDxbLinkage);

         for (int i = 0; i < astrRoute.GetSize(); i++)
         {
            CString strDxbInstanceToCheck = astrRoute[i].Mid(1, (astrRoute[i].GetLength() - 2));   // strip off " at beginning and end of string
 
            CString strDxbPath;
            strDxbPath.Format(TEXT("%s\\%s"), DXB_REGKEY_CCS_SERVICES, strDxbInstanceToCheck);

				// Copy the ServiceName Value to the new Device Value
				HKEY hParameterKey = NULL;
				CString strParamtersPath;
				CString strServiceName;
            strParamtersPath.Format(TEXT("%s\\%s\\%s"),
								DXB_REGKEY_CCS_SERVICES,
								strDxbInstanceToCheck,
								DXB_REGVAL_PARAMETERS
								);

				RegOpenKey(HKEY_LOCAL_MACHINE, strParamtersPath, &hParameterKey);

				if (hParameterKey)
				{
               Affirm(RegReadStrValue(hParameterKey, DXB_REGVAL_SERVICENAME, strServiceName));
					Affirm(RegWriteStrValue(hParameterKey, DXB_REGVAL_DEVICE, strServiceName));
					RegCloseKey(hParameterKey);
				}

            Affirm(DuplicateAdapterToDevice(HKEY_LOCAL_MACHINE, strDxbPath));

            CString strWanxPath;
            strWanxPath.Format(TEXT("%s\\%s\\%s\\%s"),
								DXB_REGKEY_CCS_SERVICES,
								strDxbInstanceToCheck,
								DXB_REGVAL_PARAMETERS,
								DXB_REGVAL_WANX
								);

				Affirm(ConvertAdapterWanxSnids(HKEY_LOCAL_MACHINE, strWanxPath));
         }
      }

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

	if (hKey)
	{
		RegCloseKey(hKey);
	}

	if (hKey)
	{
		RegCloseKey(hKey);
	}

   return fSuccess;
}


static BOOL FixWanxRouterSnids(void)
{
   BOOL fSuccess	= FALSE;
	HKEY hKey		= NULL;

   try
   {
      HKEY hKeyDigiWanxLinkage = NULL;
      if (RegOpenKey(HKEY_LOCAL_MACHINE, DXB_REGKEY_CCS_SERVICES TEXT("\\DigiWanx\\") DXB_REGKEY_LINKAGE, &hKeyDigiWanxLinkage))
      {
         CStringArray astrRoute;
         Affirm(RegReadMultiStrValue(hKeyDigiWanxLinkage, DXB_REGVAL_ROUTE, astrRoute));
         RegCloseKey(hKeyDigiWanxLinkage);

			// Wanx can be installed with no virtual adapter present,
			if (astrRoute.GetSize() > 0) 
			{

				// There Should only be one Wanx adapter.
				AffirmThrow(astrRoute.GetSize() == 1);

				CString strWanxInstanceToCheck = astrRoute[0].Mid(1, (astrRoute[0].GetLength() - 2));   // strip off " at beginning and end of string
 
				CString strWanxSnids;
				strWanxSnids.Format(TEXT("%s\\%s\\%s"),
									DXB_REGKEY_CCS_SERVICES,
									strWanxInstanceToCheck,
									DXB_REGVAL_PARAMETERS
									);

				if (RegOpenKey(HKEY_LOCAL_MACHINE, strWanxSnids, &hKey))
				{
 					CStringArray astrSnid;
 					CStringArray astrNewSnid;

					AffirmThrow(RegReadMultiStrValue(hKey, DXB_REGVAL_SNID, astrSnid));

					for (int i = 0; i < astrSnid.GetSize(); i++)
					{
						CString strSnid;	

						// Convert the SNID
						ConvertSnid(astrSnid[i],strSnid);
						astrNewSnid.InsertAt(i, strSnid);

					}

					AffirmThrow(RegWriteMultiStrValue(hKey, DXB_REGVAL_SNID, astrNewSnid));

				}

			}

      }

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

	if (hKey)
	{
		RegCloseKey(hKey);
	}

   return fSuccess;
}

static BOOL NewAndSetAllAdapters(void)
{
   BOOL fSuccess = FALSE;

   try
   {
      HKEY hKeyNetworkCards = NULL;
      AffirmThrow(RegOpenKey(HKEY_LOCAL_MACHINE, DXB_REGKEY_NETWORKCARDS, &hKeyNetworkCards));

      try
      {
         CStringArray astrKeys;
         AffirmThrow(RegEnumSubKeyNames(hKeyNetworkCards, astrKeys));

         for (int i = 0; i < astrKeys.GetSize(); i++)
         {
            if (IsWanLinksNetworkCard(astrKeys[i]))
            {
               HKEY hKeyNetworkCard = NULL;
               if (RegOpenKey(hKeyNetworkCards, astrKeys[i], &hKeyNetworkCard))
               {
                  try
                  {
                     HKEY hKeyNetRules = NULL;
                     if (RegOpenKey(hKeyNetworkCard, DXB_REGKEY_NETRULES, &hKeyNetRules))
                     {
                        try
                        {
                           CString strInfOption;
                           AffirmThrow(RegReadStrValue(hKeyNetRules, DXB_REGVAL_INFOPTION, strInfOption));

                           CString strServiceName;
                           AffirmThrow(RegReadStrValue(hKeyNetworkCard, DXB_REGVAL_SERVICENAME, strServiceName));

                           TRACE("Updating %s adapter %s's config\n", strInfOption, strServiceName);

                           CAdapter * pAdapter = NULL;
                           AffirmThrow(NewConfigObject((CConfigObject **)&pAdapter, CConfigObject::ADAPTER, strInfOption, HKEY_LOCAL_MACHINE, CString(TEXT("SYSTEM\\CurrentControlSet\\Services\\")) + strServiceName + CString(TEXT("\\Parameters")), NULL));
                           AffirmThrow(pAdapter != NULL);

                           AffirmThrow(pAdapter->SetParameters());

                           DeleteConfigObject((CConfigObject **)&pAdapter);
                        }
                        catch (CAffirmException ae)
                        {
                           TRACE("An error occured while newing and setting adapter parameters!\n");
                        }

                        RegCloseKey(hKeyNetRules);
                     }
                  }
                  catch (CAffirmException ae)
                  {
                     TRACE("An error occured while opening network card keys!\n");
                  }

                  RegCloseKey(hKeyNetworkCard);
               }
            }
         }

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

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

   return fSuccess;
}

BOOL UpdateSoftware(void)
{
   BOOL fSuccess = FALSE;

   try
   {
      static TCHAR const * const GMT_TIME_FMT = TEXT("%a %b %d %H:%M:%S %Y GMT");
      static const CVersion verOfUpdate(DXBVER_VERSION_MAJOR, DXBVER_VERSION_MINOR, DXBVER_BUILDNUMBER);

      TRACE("Digi International Inc. Software Update Version %s\n", (LPCTSTR)verOfUpdate);
      CTime ct = CTime::GetCurrentTime();
      TRACE("Time started: %s\n", ct.FormatGmt(GMT_TIME_FMT));

      CVersion verCurrentlyInstalled;

      try
      {
         AffirmThrow(GetCurrentVersion(WANLINKS_PROJECT_NAME_TEXT, verCurrentlyInstalled));
         TRACE("Currently installed version is: %s\n", (LPCTSTR)verCurrentlyInstalled);
         if (verCurrentlyInstalled >= verOfUpdate)
         {
            TRACE("The version currently installed is newer than this one!\n");
            AffirmThrow(FALSE);
         }

         Affirm(WhackOldUpdateLogs());
         Affirm(FixInfNames());
         Affirm(FixServiceGroupOrder());
         Affirm(FixGroupInfo());
         Affirm(FixAdapterValues());
         Affirm(FixWanxRouterSnids());
         Affirm(NewAndSetAllAdapters());
         Affirm(SetAllInstalledVersions(verOfUpdate));
      }
      catch (CAffirmException ae)
      {
         TRACE("An error occured during the software update!\n");
      }

      ct = CTime::GetCurrentTime();
      TRACE("Time completed: %s\n", ct.FormatGmt(GMT_TIME_FMT));

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

   return fSuccess;
}
