text: /*++
  
  Copyright (c) 2000 Microsoft Corporation
  
  Module Name:
  
      opk.c
  
  Abstract:
  
      Common modules shared by OPK tools.  Note: Source Depot requires that we 
      publish the .h (E:\NT\admin\published\ntsetup\opklib.w) and .lib 
      (E:\NT\public\internal\admin\lib).
  
  Author:
  
      Brian Ku        (briank) 06/20/2000
      Stephen Lodwick (stelo)  06/28/2000
  
  Revision History:
  
  --*/
  #include <pch.h>
  #include <objbase.h>
  #include <tchar.h>
  #include <regstr.h>
  #include <winbom.h>
  
  
  //
  // Local Define(s):
  //
  
  #define FILE_WINBOM_INI             _T("WINBOM.INI")
  #define MAX_NAME                    50
  
  #define REG_KEY_FACTORY             _T("SOFTWARE\\Microsoft\\Factory")
  #define REG_KEY_FACTORY_STATE       REG_KEY_FACTORY _T("\\State")
  #define REG_KEY_SETUP_SETUP         REGSTR_PATH_SETUP REGSTR_KEY_SETUP
  #define REG_VAL_FACTORY_WINBOM      _T("WinBOM")
  #define REG_VAL_FACTORY_USERNAME    _T("UserName")
  #define REG_VAL_FACTORY_PASSWORD    _T("Password")
  #define REG_VAL_FACTORY_DOMAIN      _T("Domain")
  #define REG_VAL_DEVICEPATH          _T("DevicePath")
  #define REG_VAL_SOURCEPATH          _T("SourcePath")
  #define REG_VAL_SPSOURCEPATH        _T("ServicePackSourcePath")
  
  #define DIR_SYSTEMROOT              _T("%SystemDrive%\\") // This has to have the trailing backslash, don't remove.
  
  #define NET_TIMEOUT                 30000   // Time out to wait for net to start in milliseconds.
  
  //
  // Local Type Define(s):
  //
  
  typedef struct _STRLIST
  {
      LPTSTR              lpszData;
      struct _STRLIST *   lpNext;
  }
  STRLIST, *PSTRLIST, *LPSTRLIST;
  
  
  //
  // Local variables
  // 
  static WCHAR NameOrgName[MAX_NAME+1];
  static WCHAR NameOrgOrg[MAX_NAME+1];
  
  static LPTSTR CleanupDirs [] =
  {
      {_T("Win9xmig")},
      {_T("Win9xupg")},
      {_T("Winntupg")}
  };
  
  //
  // Internal Fuction Prototype(s):
  //
  
  static BOOL CheckWinbomRegKey(LPTSTR lpWinBOMPath,  DWORD cbWinbBOMPath,
                                LPTSTR lpszShare,     DWORD cbShare,
                                LPTSTR lpszUser,      DWORD cbUser,
                                LPTSTR lpszPass,      DWORD cbPass,
                                LPTSTR lpFactoryMode, LPTSTR lpKey,
                                BOOL bNetwork,        LPBOOL lpbExists);
  static BOOL SearchRemovableDrives(LPTSTR lpWinBOMPath, DWORD cbWinbBOMPath, LPTSTR lpFactoryMode, UINT uDriveType);
  static BOOL WinBOMExists(LPTSTR lpWinBom, LPTSTR lpMode);
  
  static void SavePathList(HKEY hKeyRoot, LPTSTR lpszSubKey, LPSTRLIST lpStrList, BOOL bWrite);
  static BOOL AddPathToList(LPTSTR lpszExpanded, LPTSTR lpszPath, LPSTRLIST * lplpSorted, LPSTRLIST * lplpUnsorted);
  static void EnumeratePath(LPTSTR lpszPath, LPSTRLIST * lplpSorted, LPSTRLIST * lplpUnsorted);
  static BOOL AddPathsToList(LPTSTR lpszBegin, LPTSTR lpszRoot, LPSTRLIST * lplpSorted, LPSTRLIST * lplpUnsorted, BOOL bRecursive);
  
  
  /*++
  
  Routine Description:
  
      Enable or disable a given named privilege.
  
  Arguments:
  
      PrivilegeName - supplies the name of a system privilege.
  
      Enable - flag indicating whether to enable or disable the privilege.
  
  Return Value:
  
      Boolean value indicating whether the operation was successful.
  
  --*/
  BOOL
  EnablePrivilege(
      IN PCTSTR PrivilegeName,
      IN BOOL   Enable
      )
  {
      HANDLE Token;
      BOOL bRet;
      TOKEN_PRIVILEGES NewPrivileges;
      LUID Luid;
  
      if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&Token)) {
          return(FALSE);
      }
  
      if(!LookupPrivilegeValue(NULL,PrivilegeName,&Luid)) {
          CloseHandle(Token);
          return(FALSE);
      }
  
      NewPrivileges.PrivilegeCount = 1;
      NewPrivileges.Privileges[0].Luid = Luid;
      NewPrivileges.Privileges[0].Attributes = Enable ? SE_PRIVILEGE_ENABLED : 0;
  
      bRet = AdjustTokenPrivileges(
              Token,
              FALSE,
              &NewPrivileges,
              0,
              NULL,
              NULL
              );
      //
      // The return value of AdjustTokenPrivileges() can be true even though we didn't set all
      // the privileges that we asked for.  We need to call GetLastError() to make sure the call succeeded.
      //
      bRet = bRet && ( ERROR_SUCCESS == GetLastError() );
  
      CloseHandle(Token);
  
      return(bRet);
  }
  
  
  /*++
  ===============================================================================
  Routine Description:
  
      This routine will clean up any registry changes that we made to facilitate
      the factory pre-install process
  
  Arguments:
  
      none
      
  Return Value:
  
  ===============================================================================
  --*/
  void  CleanupRegistry
  (
      void
  )
  {
      HKEY        hSetupKey;
      DWORD       dwResult;
      DWORD       dwValue = 0;
                      
      // Open HKLM\System\Setup
      dwResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                              L"SYSTEM\\Setup",
                              0,
                              KEY_ALL_ACCESS,
                              &hSetupKey);
      if (NO_ERROR == dwResult)
      {
          // Set the SystemSetupInProgress Value to 0.
          RegSetValueEx(hSetupKey,
                        L"SystemSetupInProgress",
                        0,
                        REG_DWORD,
                        (LPBYTE) &dwValue,
                        sizeof(DWORD));
          
          dwValue = 0;
          RegSetValueEx(hSetupKey,
                        L"SetupType",
                        0,
                        REG_DWORD,
                        (CONST BYTE *)&dwValue,
                        sizeof(DWORD));
  
          
          
          // Delete the FactoryPreInstall value
          RegDeleteValue(hSetupKey, L"FactoryPreInstallInProgress");
          RegDeleteValue(hSetupKey, L"AuditInProgress");
          
          // Close the setup reg key
          RegCloseKey(hSetupKey);
      }
  
      return;
  }
  
  //
  // Get Organization and Owner names from registry
  //
  void GetNames(TCHAR szNameOrgOrg[], TCHAR szNameOrgName[])
  {
      HKEY  hKey = NULL;
      DWORD dwLen =  0;
      if (ERROR_SUCCESS == RegOpenKey(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), &hKey)) {            
          dwLen = MAX_NAME;
          RegQueryValueEx(hKey, TEXT("RegisteredOrganization"), 0, 0, (LPBYTE)szNameOrgOrg, &dwLen);
  
          dwLen = MAX_NAME;
          RegQueryValueEx(hKey, TEXT("RegisteredOwner"), 0, 0, (LPBYTE)szNameOrgName, &dwLen);
  
          RegCloseKey(hKey);
      }
  }
  
  //
  // Strip out non alphabets from guid
  //
  DWORD StripDash(TCHAR *pszGuid)
  {
      TCHAR *pszOrg, *pszTemp = pszGuid;
      pszOrg = pszGuid;
  
      while (pszGuid && *pszGuid != TEXT('\0')) {
          if (*pszTemp != TEXT('-') && *pszTemp != TEXT('{') && *pszTemp != TEXT('}'))
              *pszGuid++ = *pszTemp++;
          else
              pszTemp++;
      }
      if (pszOrg)
          return (DWORD)lstrlen(pszOrg);
  
      return 0;
  }
  
  //
  // GenUniqueName - Create a random computer name with a base name of 8 chars
  //
  VOID GenUniqueName(
          OUT PWSTR GeneratedString,
          IN  DWORD DesiredStrLen
          )
  {
      GUID  guid;
      DWORD total = 0, length = 0;
      TCHAR szGuid[MAX_PATH];
  
      // If we have a valid out param 
      //
      if (GeneratedString) {
  
          static PCWSTR UsableChars = L"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  
          //
          // How many characters will come from the org/name string.
          //
          DWORD   BaseLength = 8;
          DWORD   i,j;
          DWORD   UsableCount;
  
          if( DesiredStrLen < BaseLength ) {
              BaseLength = DesiredStrLen - 1;
          }
  
          // 
          // Get the Organization and Owner name from registry
          //
          GetNames(NameOrgOrg, NameOrgName);
  
          if( NameOrgOrg[0] ) {
              wcscpy( GeneratedString, NameOrgOrg );
          } else if( NameOrgName[0] ) {
              wcscpy( GeneratedString, NameOrgName );
          } else {
              wcscpy( GeneratedString, TEXT("X") );
              for( i = 1; i < BaseLength; i++ ) {
                  wcscat( GeneratedString, TEXT("X") );
              }
          }
  
          //
          // Get him upper-case for our filter...
          //
          CharUpper(GeneratedString);
  
          //
          // Now we want to put a '-' at the end
          // of our GeneratedString.  We'd like it to
          // be placed in the BASE_LENGTH character, but
          // the string may be shorter than that, or may
          // even have a ' ' in it.  Figure out where to
          // put the '-' now.
          //
          for( i = 0; i <= BaseLength; i++ ) {
  
              //
              // Check for a short string.
              //
              if( (GeneratedString[i] == 0   ) ||
                  (GeneratedString[i] == L' ') ||
                  (!wcschr(UsableChars, GeneratedString[i])) ||
                  (i == BaseLength      )
                ) {
                  GeneratedString[i] = L'-';
                  GeneratedString[i+1] = 0;
                  break;
              }
          }
  
          //
          // Special case the scenario where we had no usable
          // characters.
          //
          if( GeneratedString[0] == L'-' ) {
              GeneratedString[0] = 0;
          }
  
          total = lstrlen(GeneratedString);
  
          // Loop until we have meet the desired string length
          //
          while (total < DesiredStrLen) {
  
              // Create a unique guid to be used in the string
              //
              CoCreateGuid(&guid);
              StringFromGUID2(&guid, szGuid, AS(szGuid));
  
              // Remove the curly brace and dashes to generate the string
              //
              length = StripDash(szGuid);
              total += length;
              if (!lstrlen(GeneratedString)) {
                  if (DesiredStrLen < total)
                      lstrcpyn(GeneratedString, szGuid, DesiredStrLen+1); /* +1 for NULL */
                  else
                      lstrcpy(GeneratedString, szGuid);
              }
              else if (total < DesiredStrLen)
                  lstrcat(GeneratedString, szGuid);
              else
                  _tcsncat(GeneratedString, szGuid, (length - (total - DesiredStrLen)));
          }
  
          CharUpper(GeneratedString);
          
          // Assert if (total != DesiredStrLen)
          //
      }
  }
  
  
  /****************************************************************************\
  
  BOOL                        // Returns TRUE if any credentials are found and
                              // are going to be returned.
  
  GetCredentials(             // Tries to get user credentials from a few
                              // different places.
  
      LPTSTR  lpszUsername,   // Pointer to a string buffer that will recieve
                              // the user name for the credentials found.
  
      DWORD   cbUsername,     // Size, in characters, of the lpszUsername
                              // string buffer.
  
      LPTSTR  lpszPassword,   // Pointer to a string buffer that will recieve
                              // the password for the credentials found.
  
      DWORD   cbPassword,     // Size, in characters, of the lpszPassword
                              // string buffer.
  
      LPTSTR  lpFileName,     // Optional pointer to the file name that will
                              // contain the credentials.  If this is NULL or
                              // an empty string, the know registry key will
                              // be checked instead for the credentials.
  
      LPTSTR  lpAlternate     // Optional pointer to the alternate section to
                              // check first if lpFileName is vallid, or the
                              // the optional registry key to check instead of
                              // the normal known one.
  
  );
  
  \****************************************************************************/
  
  BOOL GetCredentials(LPTSTR lpszUsername, DWORD cbUsername, LPTSTR lpszPassword, DWORD cbPassword, LPTSTR lpFileName, LPTSTR lpAlternate)
  {
      BOOL  bRet = FALSE;
      TCHAR szUsername[UNLEN + 1] = NULLSTR,
            szPassword[PWLEN + 1] = NULLSTR,
            szDomain[DNLEN + 1]   = NULLSTR;
      LPSTR lpUserName;
      HKEY  hKey;
      DWORD dwType,
            dwSize;
      BOOL  bAlternate = ( lpAlternate && *lpAlternate );
  
      // Make sure there is a filename, otherwise we check the registry.
      //
      if ( lpFileName && *lpFileName )
      {
          // First try the alternate key for a user name.
          //
          if ( bAlternate )
          {
              GetPrivateProfileString(lpAlternate, INI_VAL_WBOM_USERNAME, NULLSTR, szUsername, AS(szUsername), lpFileName);
          }
  
          // If none found, try the normal section.  If they happen
          // to pass the normal section in as the alternate section then
          // we will check it twice if no key exists, no big deal.
          //
          if ( NULLCHR == szUsername[0] )
          {
              lpAlternate = WBOM_FACTORY_SECTION;
              GetPrivateProfileString(lpAlternate, INI_VAL_WBOM_USERNAME, NULLSTR, szUsername, AS(szUsername), lpFileName);
          }
  
          // Make sure we found a user name.
          //
          if ( szUsername[0] )
          {
              // If there is now backslash in the username, and there is a domain key, use that as the domain
              //
              if ( ((StrChr( szUsername, CHR_BACKSLASH )) == NULL) &&
                   (GetPrivateProfileString(lpAlternate, INI_VAL_WBOM_DOMAIN, NULLSTR, szDomain, AS(szDomain), lpFileName)) && szDomain[0]
                 )
              {
                  // Copy the "domain\username" string into the returning buffer
                  //
                  lstrcpyn(lpszUsername, szDomain, cbUsername);
                  AddPathN(lpszUsername, szUsername, cbUsername);
              }
              else
              {
                  // Copy the username into the returning buffer
                  //
                  lstrcpyn(lpszUsername, szUsername, cbUsername);
              }
  
              // We found the credentials
              //
              bRet = TRUE;
  
              // Get the password
              //
              if ( GetPrivateProfileString(lpAlternate, INI_VAL_WBOM_PASSWORD, NULLSTR, szPassword, AS(szPassword), lpFileName) )
              {
                  // Copy the password into the returning buffer
                  //
                  lstrcpyn(lpszPassword, szPassword, cbPassword);
              }
              else
                  *lpszPassword = NULLCHR;
  
          }
      }
      else if ( RegOpenKeyEx(HKEY_LOCAL_MACHINE, bAlternate ? lpAlternate : REG_KEY_FACTORY, 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS )
      {
          // Check the registry key to see if it has user credentials.
          //
          dwSize = sizeof(szUsername);
          if ( ( RegQueryValueEx(hKey, REG_VAL_FACTORY_USERNAME, NULL, &dwType, (LPBYTE) szUsername, &dwSize) == ERROR_SUCCESS ) &&
               ( dwType == REG_SZ ) &&
               ( szUsername[0] ) )
          {
              // Check the registry key to see if it has user credentials.
              //
              dwSize = sizeof(szDomain);
              if ( ( StrChr(szUsername, CHR_BACKSLASH) == NULL ) &&
                   ( RegQueryValueEx(hKey, REG_VAL_FACTORY_DOMAIN, NULL, &dwType, (LPBYTE) szDomain, &dwSize) == ERROR_SUCCESS ) &&
                   ( dwType == REG_SZ ) &&
                   ( szDomain[0] ) )
              {
                  // Copy the domain and username into the returning buffer.
                  //
                  AddPathN(szDomain, szUsername, AS(szDomain));
                  lstrcpyn(lpszUsername, szDomain, cbUsername);
              }
              else
              {
                  // Copy the username into the returning buffer.
                  //
                  lstrcpyn(lpszUsername, szUsername, cbUsername);
              }
  
              // We found the credentials
              //
              bRet = TRUE;
  
              // Check the registry key to see if it has user credentials.
              //
              dwSize = sizeof(szPassword);
              if ( ( RegQueryValueEx(hKey, REG_VAL_FACTORY_PASSWORD, NULL, &dwType, (LPBYTE) szPassword, &dwSize) == ERROR_SUCCESS ) &&
                   ( dwType == REG_SZ ) )
              {
                  // Copy the password into the returning buffer
                  //
                  lstrcpyn(lpszPassword, szPassword, cbPassword);
              }
              else
              {
                  // No password specified, just return and empty string.
                  //
                  *lpszPassword = NULLCHR;
              }
          }
  
          // Always remember to close the key.
          //
          RegCloseKey(hKey);
      }
      
      return bRet;
  }
  
  
  NET_API_STATUS FactoryNetworkConnectEx(LPTSTR lpszPath, LPTSTR lpszWinBOMPath, LPTSTR lpAlternateSection, LPTSTR lpszUsername, DWORD cbUsername, LPTSTR lpszPassword, DWORD cbPassword, BOOL bState)
  {
      NET_API_STATUS  nErr,
                      nRet                                = 0;
      static BOOL     bFirst                              = TRUE;
      BOOL            bJustStarted                        = FALSE;
      TCHAR           szUsername[UNLEN + DNLEN + 2]       = NULLSTR,
                      szPassword[PWLEN + 1]               = NULLSTR,
                      szBuffer[MAX_PATH],
                      szWinbomShare[MAX_PATH]             = NULLSTR;
      LPTSTR          lpSearch;
      DWORD           dwStart;
  
      // Get the credentials for the current section
      //
      if ( bState )
      {
          // Make sure we pass in a username buffer big enough to hold the "domain\username" string.
          //
          GetCredentials(szUsername, AS(szUsername), szPassword, AS(szPassword), lpszWinBOMPath, lpAlternateSection);
      }
  
      // Get just the share pare of the winbom path if it is a UNC.
      //
      if ( lpszWinBOMPath )
      {
          GetUncShare(lpszWinBOMPath, szWinbomShare, AS(szWinbomShare));
      }
  
      // Determine all of the UNC paths in the string supplied
      //
      lpSearch = lpszPath;
      while ( lpSearch = StrStr(lpSearch, _T("\\\\")) )
      {
          // See if this is a UNC share.
          //
          if ( GetUncShare(lpSearch, szBuffer, AS(szBuffer)) && szBuffer[0] )
          {
              // We can not connect or disconnect from the share where the winbom is.
              //
              if ( ( NULLCHR == szWinbomShare[0] ) ||
                   ( lstrcmpi(szBuffer, szWinbomShare) != 0 ) )
              {
                  // Connect/disconnect from the share and 
                  //
                  nErr = 0;
                  dwStart = GetTickCount();
                  do
                  {
                      if ( nErr )
                      {
                          Sleep(100);
                      }
                      if ( NERR_WkstaNotStarted == (nErr = ConnectNetworkResource(szBuffer, szUsername, szPassword, bState)) )
                      {
                          // Wierd bug here we are hacking around.  If we just wait till the network starts, sometimes the first
                          // call it gives us a wierd error.  So if we run into the not started error, then we keep retrying on
                          // any error until we time out.
                          //
                          bJustStarted = TRUE;
                      }
  #ifdef DBG
                      LogFileStr(_T("c:\\sysprep\\winbom.log"), _T("FactoryNetworkConnect(%s)=%d [%d,%d]\n"), szBuffer, nErr, dwStart, GetTickCount());
  #endif // DBG
                  }
                  while ( ( bFirst && bJustStarted && nErr ) &&
                          ( (GetTickCount() - dwStart) < NET_TIMEOUT ) );
  
                  // If we hit an error and it is the first one,
                  // return it.
                  //
                  if ( nErr && ( 0 == nRet ) )
                  {
                      nRet = nErr;
                  }
  
                  // Once we have tried to connect to a network resource, we set
                  // this so we don't ever time out again as long as we are still
                  // running.
                  //
                  bFirst = FALSE;
              }
  
              // Move the pointer past the share name.
              //
              lpSearch += lstrlen(szBuffer);
          }
          else
          {
              // Go past the double backslash even though it is not a UNC path.
              //
              lpSearch += 2;
          }
      }
  
      // Might need to return the credentials we used.
      //
      if ( lpszUsername && cbUsername )
      {
          lstrcpyn(lpszUsername, szUsername, cbUsername);
      }
      if ( lpszPassword && cbPassword )
      {
          lstrcpyn(lpszPassword, szPassword, cbPassword);
      }
  
      // Return the net error, or 0 if everything worked.
      //
      return nRet;
  }
  
  
  NET_API_STATUS FactoryNetworkConnect(LPTSTR lpszPath, LPTSTR lpszWinBOMPath, LPTSTR lpAlternateSection, BOOL bState)
  {
      return FactoryNetworkConnectEx(lpszPath, lpszWinBOMPath, lpAlternateSection, NULL, 0, NULL, 0, bState);
  }
  
  
  
  /*++
  ===============================================================================
  Routine Description:
  
      This routine will locate the WINBOM.INI file. The search algorithm will be:
          * Check the registry key
          * Check local floppy drives
          * Check local CD-ROM drives
          * Check the sysprep folder
          * Check the root of the boot volume
  Arguments:
  
      lpWinBOMPath - return buffer where the winbom path will be copied.
      cbWinbBOMPath - size of return buffer in characters.
      lpFactoryPath - the sysprep folder where factory.exe is.
  
  Return Value:
  
      TRUE - WINBOM.INI was found
  
      FALSE - WINBOM.INI could not be found
  
  ===============================================================================
  --*/
  
  BOOL LocateWinBom(LPTSTR lpWinBOMPath, DWORD cbWinbBOMPath, LPTSTR lpFactoryPath, LPTSTR lpFactoryMode, DWORD dwFlags)
  {
      BOOL    bFound              = FALSE,
              bRunningFromCd,
              bNetwork            = !(GET_FLAG(dwFlags, LOCATE_NONET));
      TCHAR   szWinBom[MAX_PATH]  = NULLSTR;
      TCHAR   szNewShare[MAX_PATH],
              szNewUser[256],
              szNewPass[256],
              szCurShare[MAX_PATH] = NULLSTR,
              szCurUser[256]       = NULLSTR,
              szCurPass[256]       = NULLSTR;
  
      // Set the error mode so no drives display error messages ("please insert floppy")
      //
      SetErrorMode(SEM_FAILCRITICALERRORS);
  
      // Always set the return buffer to an empty string first.
      //
      *lpWinBOMPath = NULLCHR;
  
      // This neat little flag is used when we think that we already found a
      // winbom sometime this boot (like when factory runs from the run key) and
      // we want to make sure we use the same winbom instead of searching again
      // and maybe getting a different one.
      //
      if ( GET_FLAG(dwFlags, LOCATE_AGAIN) )
      {
          BOOL bDone;
  
          // Try our state winbom key to see if we have a winbom that we are already using.
          //
          bFound = CheckWinbomRegKey(szWinBom, AS(szWinBom), szCurShare, AS(szCurShare), szCurUser, AS(szCurUser), szCurPass, AS(szCurPass), lpFactoryMode, REG_KEY_FACTORY_STATE, bNetwork, &bDone);
  
          // Now if the registry key existed, we don't want to do anything more.  Just use
          // what we got right now and be done with it.
          //
          if ( bDone )
          {
              // Check to see if we have anything to return.
              //
              if ( bFound )
              {
                  // Copy the path we found into the return buffer.
                  //
                  lstrcpyn(lpWinBOMPath, szWinBom, cbWinbBOMPath);
              }
  
              // Set the error mode back to system default
              //
              SetErrorMode(0);
  
              // Return right now if we found it or not.
              //
              return bFound;
          }
      }
  
      // Check to see if the system drive is a CD-ROM (which pretty much
      // means that we are running in WinPE and we should search this drive
      // last).
      //
      ExpandEnvironmentStrings(DIR_SYSTEMROOT, szWinBom, AS(szWinBom));
      bRunningFromCd = ( GetDriveType(szWinBom) == DRIVE_CDROM );
      szWinBom[0] = NULLCHR;
  
      // Check the magic registry key first as an option to override were the winbom is.
      //
      bFound = CheckWinbomRegKey(szWinBom, AS(szWinBom), szCurShare, AS(szCurShare), szCurUser, AS(szCurUser), szCurPass, AS(szCurPass), lpFactoryMode, REG_KEY_FACTORY, bNetwork, NULL);
  
      // Walk through the drives first checking if the drive is removeable and NOT CDROM.
      // Check for the presence of the WinBOM file and quit if it is found.
      //
      if ( !bFound )
      {
          bFound = SearchRemovableDrives(szWinBom, AS(szWinBom), lpFactoryMode, DRIVE_REMOVABLE);
      }
  
      // Walk through the drives again this time checking if the drive IS a CD-ROM.
      // Check for the presence of the WinBOM file and quit if it is found.  Also
      // only do this if the OS is not running from a CD.  This is so in the WinPE
      // case the person can put a winbom on the hard drive and it will be used
      // before the one that is always on the CD-ROM that WinPE boots from.
      //
      if ( !bFound )
      {
          bFound = SearchRemovableDrives(szWinBom, AS(szWinBom), lpFactoryMode, bRunningFromCd ? DRIVE_FIXED : DRIVE_CDROM);
      }
  
      // Now if still not found, check the same directory as factory.
      //
      if ( !bFound )
      {
          lstrcpyn(szWinBom, lpFactoryPath, AS(szWinBom));
          AddPath(szWinBom, FILE_WINBOM_INI);
          bFound = WinBOMExists(szWinBom, lpFactoryMode);
      }
  
      // Now if still not found, check the root of the system drive.
      //
      if ( !bFound )
      {
          ExpandEnvironmentStrings(DIR_SYSTEMROOT, szWinBom, AS(szWinBom));
          lstrcat(szWinBom, FILE_WINBOM_INI);
          bFound = WinBOMExists(szWinBom, lpFactoryMode);
      }
  
      // Now if we skipped the CD-ROM search above, do it now if we still
      // don't have a winbom.
      //
      if ( !bFound && bRunningFromCd )
      {
          bFound = SearchRemovableDrives(szWinBom, AS(szWinBom), lpFactoryMode, DRIVE_CDROM);
      }
  
      // Make sure we found a WinBOM and look for a NewWinbom key.
      //
      if ( bFound )
      {
          DWORD   dwLimit = 10;  // Must be greater than zero.
          BOOL    bAgain;
          LPTSTR  lpszNewWinbom;
  
          // Copy the path we found into the return buffer.
          //
          lstrcpyn(lpWinBOMPath, szWinBom, cbWinbBOMPath);
  
          // Now do the loop to search for possible NewWinBom keys.
          //
          do
          {
              // Reset the bool so we can check for alternate WinBOMs.
              //
              bAgain = FALSE;
  
              // See if the NewWinBom key exists in the winbom we found.
              //
              if ( lpszNewWinbom = IniGetExpand(lpWinBOMPath, INI_SEC_WBOM_FACTORY, INI_KEY_WBOM_FACTORY_NEWWINBOM, NULL) )
              {
                  LPTSTR  lpShareRemove;
                  BOOL    bSame = FALSE;
  
                  // The NewWinBom key might be a UNC, so see if we need to connect
                  // to the share.
                  //
                  szNewShare[0] = NULLCHR;
                  szNewUser[0] = NULLCHR;
                  szNewPass[0] = NULLCHR;
                  if ( bNetwork && GetUncShare(lpszNewWinbom, szNewShare, AS(szNewShare)) && szNewShare[0] )
                  {
                      // Only really need to connect if the we are not already
                      // connected.
                      //
                      if ( lstrcmpi(szNewShare, szCurShare) != 0 )
                      {
                          FactoryNetworkConnectEx(szNewShare, lpWinBOMPath, NULL, szNewUser, AS(szNewUser), szNewPass, AS(szNewPass), TRUE);
                      }
                      else
                      {
                          bSame = TRUE;
                      }
                  }
  
                  // Now make sure the winbom we found really exists and is a vallid
                  // winbom we can use.
                  //
                  if ( WinBOMExists(lpszNewWinbom, lpFactoryMode) )
                  {
                      // Copy the new winbom path we found into the return buffer.
                      //
                      lstrcpyn(lpWinBOMPath, lpszNewWinbom, cbWinbBOMPath);
  
                      bAgain = TRUE;
                      lpShareRemove = szCurShare;
                  }
                  else
                  {
                      lpShareRemove = szNewShare;
                  }
  
                  // Do any share cleanup that needs to happen.
                  //
                  if ( bNetwork && *lpShareRemove && !bSame )
                  {
                      FactoryNetworkConnect(lpShareRemove, NULL, NULL, FALSE);
                      *lpShareRemove = NULLCHR;
                  }
  
                  // Also save the share info if there was one so we can cleanup
                  // later if we find another winbom.
                  //
                  if ( bAgain )
                  {
                      lstrcpyn(szCurShare, szNewShare, AS(szCurShare));
                      lstrcpyn(szCurUser, szNewUser, AS(szCurUser));
                      lstrcpyn(szCurPass, szNewPass, AS(szCurPass));
                  }
  
                  // Clean up the ini key we allocated.
                  //
                  FREE(lpszNewWinbom);
              }
          }
          while ( --dwLimit && bAgain );
      }
  
      // Save the winbom we are using (or empty string if not using one) to our state
      // key so other programs or instances of factory running this boot know what winbom
      // to use.
      //
      RegSetString(HKLM, REG_KEY_FACTORY_STATE, REG_VAL_FACTORY_WINBOM, lpWinBOMPath);
  
      // We may want to save the credentials that we used to get to this winbom in our
      // state key so we can get them back and reconnect, but for now if we just don't
      // ever disconnect from the share where the winbom is, we should be fine.  Only need
      // to worry about this if the caller of the function wanted to disconnect this
      // network resource when they were done.  If we do that, these keys will have to
      // be written.  But we will have to put in a bunch of code so that CheckWinbomRegKey()
      // returns the credentials used, and when doing our NewWinBom search that we also
      // save the credentials we finally end up using.  This is a lot of work, and I see
      // no need for this now.  Just make sure if you call this function that you do NOT
      // call FactoryNetworkConnect() to remove the net connection.
      //
      // This didn't work, because when we log on we loose our net connection so we need
      // the credentials to reconnect after logon.  So I went through all the work I mentioned
      // above to make this work.
      //
      RegSetString(HKLM, REG_KEY_FACTORY_STATE, REG_VAL_FACTORY_USERNAME, szCurUser);
      RegSetString(HKLM, REG_KEY_FACTORY_STATE, REG_VAL_FACTORY_PASSWORD, szCurPass);
  
      // Set the error mode back to system default
      //
      SetErrorMode(0);
  
      // Return if we found it or not.
      //
      return bFound;
  }
  
  BOOL SetFactoryStartup(LPCTSTR lpFactory)
  {
      HKEY    hKey;
      BOOL    bRet    = TRUE;
  
      // Now make sure we are also setup as a setup program to run before we log on.
      //
      if ( RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SYSTEM\\Setup"), 0, KEY_ALL_ACCESS, &hKey ) == ERROR_SUCCESS )
      {
          TCHAR   szFileName[MAX_PATH + 32]   = NULLSTR;
          DWORD   dwVal;
  
          //
          // Setup the control flags for the SETUP key
          // The Setting used are:
          //      CmdLine = c:\sysprep\factory.exe -setup
          //      SetupType = 2 (No reboot)
          //      SystemSetupInProgress = 0 (no service restrictions)
          //      MiniSetupInProgress = 0 (Not doing a mini setup)
          //      FactoryPreInstallInProgress = 1 (Delay pnp driver installs)
          //      AuditInProgress = 1 (general key to determine if the OEM is auditing the machine)
          //
  
          lstrcpyn(szFileName, lpFactory, AS(szFileName));
          lstrcat(szFileName, _T(" -setup"));
          if ( RegSetValueEx(hKey, _T("CmdLine"), 0, REG_SZ, (CONST LPBYTE) szFileName, ( lstrlen(szFileName) + 1 ) * sizeof(TCHAR)) != ERROR_SUCCESS )
              bRet = FALSE;
  
          dwVal = SETUPTYPE_NOREBOOT;
          if ( RegSetValueEx(hKey, TEXT("SetupType"), 0, REG_DWORD, (CONST LPBYTE) &dwVal, sizeof(DWORD)) != ERROR_SUCCESS )
              bRet = FALSE;
  
          dwVal = 0;
          if ( RegSetValueEx(hKey, TEXT("SystemSetupInProgress"), 0, REG_DWORD, (CONST LPBYTE) &dwVal, sizeof(DWORD)) != ERROR_SUCCESS )
              bRet = FALSE;
  
          dwVal = 0;
          if ( RegSetValueEx(hKey, TEXT("MiniSetupInProgress"), 0, REG_DWORD, (CONST LPBYTE) &dwVal, sizeof(DWORD)) != ERROR_SUCCESS )
              bRet = FALSE;
  
          dwVal = 1;
          if ( RegSetValueEx(hKey, TEXT("FactoryPreInstallInProgress"), 0, REG_DWORD, (CONST LPBYTE) &dwVal, sizeof(DWORD)) != ERROR_SUCCESS )
              bRet = FALSE;
  
          dwVal = 1;
          if ( RegSetValueEx(hKey, TEXT("AuditInProgress"), 0, REG_DWORD, (CONST LPBYTE) &dwVal, sizeof(DWORD)) != ERROR_SUCCESS )
              bRet = FALSE;
  
          RegCloseKey(hKey);
      }
      else
          bRet = FALSE;
  
      return bRet;
  }
  
  BOOL UpdateDevicePathEx(HKEY hKeyRoot, LPTSTR lpszSubKey, LPTSTR lpszNewPath, LPTSTR lpszRoot, BOOL bRecursive)
  {
      LPSTRLIST   lpSorted = NULL,
                  lpUnsorted = NULL;
      LPTSTR      lpszDevicePath;
  
      // First add any paths already in the registry to the lists.
      //
      if ( lpszDevicePath = RegGetString(hKeyRoot, lpszSubKey, REG_VAL_DEVICEPATH) )
      {
          AddPathsToList(lpszDevicePath, NULL, &lpSorted, &lpUnsorted, FALSE);
          FREE(lpszDevicePath);
      }
  
      // Now add any they wanted to the list.
      //
      AddPathsToList(lpszNewPath, lpszRoot, &lpSorted, &lpUnsorted, bRecursive);
  
      // Now that we are done, we can free our sorted list.
      //
      SavePathList(hKeyRoot, lpszSubKey, lpSorted, FALSE);
  
      // Now save our final list back to the registry and free
      // it.
      //
      SavePathList(hKeyRoot, lpszSubKey, lpUnsorted, TRUE);
  
      return TRUE;
  }
  
  BOOL UpdateDevicePath(LPTSTR lpszNewPath, LPTSTR lpszRoot, BOOL bRecursive)
  {
      return ( UpdateDevicePathEx( HKLM, 
                                   REGSTR_PATH_SETUP, 
                                   lpszNewPath, 
                                   lpszRoot ? lpszRoot : DIR_SYSTEMROOT, 
                                   bRecursive ) );
  }
  
  BOOL UpdateSourcePath(LPTSTR lpszSourcePath)
  {
      BOOL bRet = FALSE;
  
      if ( lpszSourcePath && *lpszSourcePath )
      {
          if (bRet = RegSetString(HKLM, REG_KEY_SETUP_SETUP, REG_VAL_SOURCEPATH, lpszSourcePath))
          {
              bRet = RegSetString(HKLM, REG_KEY_SETUP_SETUP, REG_VAL_SPSOURCEPATH, lpszSourcePath);
          }
      }
  
      return bRet;
  }
  
  
  //
  // Internal Function(s):
  //
  
  static BOOL CheckWinbomRegKey(LPTSTR lpWinBOMPath,  DWORD cbWinbBOMPath,
                                LPTSTR lpszShare,     DWORD cbShare,
                                LPTSTR lpszUser,      DWORD cbUser,
                                LPTSTR lpszPass,      DWORD cbPass,
                                LPTSTR lpFactoryMode, LPTSTR lpKey,
                                BOOL bNetwork,        LPBOOL lpbExists)
  {
      HKEY    hKey;
      BOOL    bFound              = FALSE,
              bExists             = FALSE;
      TCHAR   szWinBom[MAX_PATH]  = NULLSTR;
  
      // Check the registry key to see if it knows about a winbom to use.
      //
      if ( RegOpenKeyEx(HKEY_LOCAL_MACHINE, lpKey, 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS )
      {
          DWORD   dwType,
                  dwSize = sizeof(szWinBom);
  
          // Try to get the value.
          //
          if ( ( RegQueryValueEx(hKey, REG_VAL_FACTORY_WINBOM, NULL, &dwType, (LPBYTE) szWinBom, &dwSize) == ERROR_SUCCESS ) &&
               ( dwType == REG_SZ ) )
          {
              // Now the key must have existed.  If there was something
              // in the key, lets try to use it.
              //
              if ( szWinBom[0] )
              {
                  TCHAR   szShare[MAX_PATH]   = NULLSTR,
                          szUser[256]         = NULLSTR,
                          szPass[256]         = NULLSTR;
  
                  // Throw some networking support in here.
                  //
                  if ( bNetwork && GetUncShare(szWinBom, szShare, AS(szShare)) && szShare[0] )
                  {
                      FactoryNetworkConnectEx(szShare, NULL, lpKey, szUser, AS(szUser), szPass, AS(szPass), TRUE);
                  }
  
                  // Check to see if the winbom is actually there still.
                  // If not, then it is bad and we should just act like
                  // the key didn't even exist.
                  //
                  if ( WinBOMExists(szWinBom, lpFactoryMode) )
                  {
                      // If found, return the winbom in the supplied buffer.
                      //
                      lstrcpyn(lpWinBOMPath, szWinBom, cbWinbBOMPath);
                      bFound = bExists = TRUE;
  
                      // See what we might need to return.
                      //
                      if ( lpszShare && cbShare )
                      {
                          lstrcpyn(lpszShare, szShare, cbShare);
                      }
                      if ( lpszUser && cbUser )
                      {
                          lstrcpyn(lpszUser, szUser, cbUser);
                      }
                      if ( lpszPass && cbPass )
                      {
                          lstrcpyn(lpszPass, szPass, cbPass);
                      }
                  }
                  else if ( bNetwork && szShare[0] )
                  {
                      // Clean up our net connection.
                      //
                      FactoryNetworkConnect(szShare, NULL, NULL, FALSE);
                  }
              }
              else
              {
                  // There wasn't anything in the key, but it did exist.
                  // This most likely means we didn't find the winbom the
                  // first time around, so we may need to know that now.
                  //
                  bExists = TRUE;
              }
          }
  
          // Always remember to close the key.
          //
          RegCloseKey(hKey);
      }
  
      // If they want to know if the key existed, then return it to them.
      //
      if ( lpbExists )
      {
          *lpbExists = bExists;
      }
  
      // If we found a winbom, return true.
      //
      return bFound;
  }
  
  static BOOL SearchRemovableDrives(LPTSTR lpWinBOMPath, DWORD cbWinbBOMPath, LPTSTR lpFactoryMode, UINT uDriveType)
  {
      DWORD   dwDrives;
      TCHAR   szWinBom[MAX_PATH],
              szDrive[]           = _T("_:\\");
      BOOL    bFound              = FALSE;
  
      // Loop through all the dirves on the system.
      //
      for ( szDrive[0] = _T('A'), dwDrives = GetLogicalDrives();
            ( szDrive[0] <= _T('Z') ) && dwDrives && !bFound;
            szDrive[0]++, dwDrives >>= 1 )
      {
          // First check to see if the first bit is set (which means
          // this drive exists in the system).  Then make sure it is
          // a drive type that we want to check for a winbom.
          //
          if ( ( dwDrives & 0x1 ) &&
               ( GetDriveType(szDrive) == uDriveType ) )
          {
              // See if there is a wINBOM.INI file on the drive.
              //
              lstrcpyn(szWinBom, szDrive, AS(szWinBom));
              lstrcat(szWinBom, FILE_WINBOM_INI);
              if ( WinBOMExists(szWinBom, lpFactoryMode) )
              {
                  // Return the path to the winbom in the supplied buffer.
                  //
                  lstrcpyn(lpWinBOMPath, szWinBom, cbWinbBOMPath);
                  bFound = TRUE;
              }
          }
      }
  
      return bFound;
  }
  
  static BOOL WinBOMExists(LPTSTR lpWinBom, LPTSTR lpMode)
  {
      BOOL bRet = FALSE;
  
      // First the file must exists.
      //
      if ( FileExists(lpWinBom) )
      {
          TCHAR szModes[256] = NULLSTR;
  
          // See if there is even a mode string in this winbom (has to be or
          // we will automatically use it).
          //
          if ( lpMode &&
               *lpMode &&
               GetPrivateProfileString(WBOM_FACTORY_SECTION, INI_KEY_WBOM_FACTORY_TYPE, NULLSTR, szModes, AS(szModes), lpWinBom) &&
               szModes[0] )
          {
              LPTSTR  lpCheck = szModes,
                      lpNext;
  
              // Loop through ever comma delimited field in the value we got
              // from the winbom (there is always at least one).
              //
              do
              {
                  // See if there is another mode field in this string.
                  //
                  if ( lpNext = StrChr(lpCheck, _T(',')) )
                      *lpNext++ = NULLCHR;
  
                  // Make sure there are no spaces around the field.
                  //
                  StrTrm(lpCheck, _T(' '));
  
                  // If the mode we are in matches the one in the winbom, then
                  // we are good to go.
                  //
                  if ( lstrcmpi(lpMode, lpCheck) == 0 )
                      bRet = TRUE;
  
                  // Set the check pointer to the next
                  // field.
                  //
                  lpCheck = lpNext;
              }
              while ( !bRet && lpCheck );
  
              // It would be nice to log if we don't use this winbom because of this
              // setting, but we can't really do that because we need to winbom to
              // init logging.
              //
              /*
              if ( !bRet )
              {
                  // Log here.
              }
              */
          }
          else
              bRet = TRUE;
      }
  
      return bRet;
  }
  
  static void SavePathList(HKEY hKeyRoot, LPTSTR lpszSubKey, LPSTRLIST lpStrList, BOOL bWrite)
  {
      LPSTRLIST   lpStrListNode;
      DWORD       cbDevicePath = 256,
                  dwLength     = 0,
                  dwOldSize;
      LPTSTR      lpszDevicePath;
  
      // Initialize our intial buffer we are going to use to
      // write to the registry.
      //
      if ( bWrite )
      {
          lpszDevicePath = (LPTSTR) MALLOC(cbDevicePath * sizeof(TCHAR));
      }
  
      // Loop through the list.
      //
      while ( lpStrList )
      {
          // Save a pointer to the current node.
          //
          lpStrListNode = lpStrList;
  
          // Advanced to the next node.
          //
          lpStrList = lpStrList->lpNext;
  
          // If we are saving this list to the registry, then
          // we need to add to our buffer.
          //
          if ( bWrite && lpszDevicePath )
          {
              // Make sure our buffer is still big enough.
              // The two extra are for the possible semi-colon
              // we might add and one more to be safe.  We
              // don't have to worry about the null terminator
              // because we do less than or equal to our current
              // buffer size.
              //
              dwOldSize = cbDevicePath;
              dwLength += lstrlen(lpStrListNode->lpszData);
              while ( cbDevicePath <= (dwLength + 2) )
              {
                  cbDevicePath *= 2;
              }
  
              // If it wasn't big enough, we need to reallocate it.
              //
              if ( cbDevicePath > dwOldSize )
              {
                  LPTSTR lpszTmpDevicePath = (LPTSTR) REALLOC(lpszDevicePath, cbDevicePath * sizeof(TCHAR));
  
                  //
                  // Make sure the REALLOC succeeded before reassigning the memory
                  //
                  if ( lpszTmpDevicePath )
                  {
                      lpszDevicePath = lpszTmpDevicePath;
                  }
              }
  
              // Make sure we still have a buffer.
              //
              if ( lpszDevicePath )
              {
                  // If we already have added a path, tack on a semicolon.
                  //
                  if ( *lpszDevicePath )
                  {
                      lstrcat(lpszDevicePath, _T(";"));
                      dwLength++;
                  }
  
                  // Now add our path.
                  //
                  lstrcat(lpszDevicePath, lpStrListNode->lpszData);
              }
          }
  
          // Free the data in this node.
          //
          FREE(lpStrListNode->lpszData);
  
          // Free the node itself.
          //
          FREE(lpStrListNode);
      }
  
      // If we have the data, save it to the registry.
      //
      if ( bWrite && lpszDevicePath )
      {
          RegSetExpand(hKeyRoot, lpszSubKey, REG_VAL_DEVICEPATH, lpszDevicePath);
          FREE(lpszDevicePath);
      }
  }
  
  static BOOL AddPathToList(LPTSTR lpszExpanded, LPTSTR lpszPath, LPSTRLIST * lplpSorted, LPSTRLIST * lplpUnsorted)
  {
      LPSTRLIST   lpSortedNode,
                  lpUnsortedNode;
      BOOL        bQuit = FALSE;
  
      // Loop until we get to the end or find a string that is bigger than
      // ours.
      //
      while ( *lplpSorted && !bQuit )
      {
          // If we do this, we don't have to do the complicated
          // indirection.
          //
          lpSortedNode = *lplpSorted;
  
          // Compare the nodes string to the one we want to add.
          //
          switch ( CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, lpszExpanded, -1, lpSortedNode->lpszData, -1) )
          {
              case CSTR_EQUAL:
                  
                  // If the are the same, we just return FALSE because we do
                  // not need to add it.
                  //
                  return FALSE;
  
              case CSTR_LESS_THAN:
  
                  // If our string is less than the one in this node, we need
                  // to stop so we can insert ourself before it.
                  //
                  bQuit = TRUE;
                  break;
  
              default:
  
                  // Default, just try the next item in the list.
                  //
                  lplpSorted = &(lpSortedNode->lpNext);
          }
      }
  
      // Now we need to advance the pointer of the unsorted list to the
      // end so we can add ours.
      //
      while ( *lplpUnsorted )
      {
          lpUnsortedNode = *lplpUnsorted;
          lplpUnsorted = &(lpUnsortedNode->lpNext);
      }
  
      // Allocate our nodes.  If anything fails, we have to return false.
      //
      if ( NULL == (lpSortedNode = (LPSTRLIST) MALLOC(sizeof(STRLIST))) )
      {
          return FALSE;
      }
      if ( NULL == (lpUnsortedNode = (LPSTRLIST) MALLOC(sizeof(STRLIST))) )
      {
          FREE(lpSortedNode);
          return FALSE;
      }
  
      // Set the data in the sorted node and insert the list since we
      // know where that goes right now.
      //
      lpSortedNode->lpszData = lpszExpanded;
      lpSortedNode->lpNext = *lplpSorted;
      *lplpSorted = lpSortedNode;
  
      // Now set the data in the unsorted node and insert it at the end
      // of that list.
      //
      lpUnsortedNode->lpszData = lpszPath;
      lpUnsortedNode->lpNext = NULL;
      *lplpUnsorted = lpUnsortedNode;
  
      return TRUE;
  }
  
  static void EnumeratePath(LPTSTR lpszPath, LPSTRLIST * lplpSorted, LPSTRLIST * lplpUnsorted)
  {
      WIN32_FIND_DATA FileFound;
      HANDLE          hFile;
      LPTSTR          lpszNewPath,
                      lpszExpandedPath;
      DWORD           cbNewPath;
      BOOL            bAdded = FALSE;
  
      // Process all the files and directories in the directory passed in.
      //
      if ( (hFile = FindFirstFile(_T("*"), &FileFound)) != INVALID_HANDLE_VALUE )
      {
          do
          {
              // First check to see if this is a a directory.
              //
              if ( ( FileFound.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) &&
                   ( lstrcmp(FileFound.cFileName, _T(".")) != 0 ) &&
                   ( lstrcmp(FileFound.cFileName, _T("..")) != 0 ) &&
                   ( SetCurrentDirectory(FileFound.cFileName) ) )
              {
                  // Need the size for the new path... which is the length of the
                  // old path, plus new path, and 3 extra for the joining backslash,
                  // null terminator, and another one more to be safe.
                  //
                  cbNewPath = lstrlen(lpszPath) + lstrlen(FileFound.cFileName) + 3;
                  if ( lpszNewPath = (LPTSTR) MALLOC(cbNewPath * sizeof(TCHAR)) )
                  {
                      // Create our new path (note this one is not expanded out,
                      // it may contain environment variables.
                      //
                      lstrcpyn(lpszNewPath, lpszPath, cbNewPath);
                      AddPathN(lpszNewPath, FileFound.cFileName, cbNewPath);
  
                      // Make sure we can expand out the buffer.
                      //
                      if ( lpszExpandedPath = AllocateExpand(lpszNewPath) )
                      {
                          // Now add the path to the list.
                          //
                          bAdded = AddPathToList(lpszExpandedPath, lpszNewPath, lplpSorted, lplpUnsorted);
  
                          // If the path didn't get added, we have to free the buffer.
                          //
                          if ( !bAdded )
                          {
                              FREE(lpszExpandedPath);
                          }
                      }
  
                      // Continue the recursive search
                      //
                      EnumeratePath(lpszNewPath, lplpSorted, lplpUnsorted);
  
                      // If the path didn't get added, we have to free the buffer.
                      //
                      if ( !bAdded )
                      {
                          FREE(lpszNewPath);
                      }
                  }
  
                  // Set the current directory to parent, to continue.
                  //
                  SetCurrentDirectory(_T(".."));
              }
  
          }
          while ( FindNextFile(hFile, &FileFound) );
  
          FindClose(hFile);
      }
  }
  
  static BOOL AddPathsToList(LPTSTR lpszBegin, LPTSTR lpszRoot, LPSTRLIST * lplpSorted, LPSTRLIST * lplpUnsorted, BOOL bRecursive)
  {
      BOOL    bRet            = TRUE,
              bAddBackslash   = FALSE,
              bAdded;
      LPTSTR  lpszEnd,
              lpszPath,
              lpszExpanded,
              lpszCat;
      DWORD   dwSize,
              dwBackslash;
  
      // If they don't pass in the root we don't do anything.
      //
      if ( lpszRoot )
      {
          if ( NULLCHR == *lpszRoot )
          {
              lpszRoot = NULL;
          }
          else if ( _T('\\') != *CharPrev(lpszRoot, lpszRoot + lstrlen(lpszRoot)) )
          {
              // The root path passed in doesn't have a backslash at
              // the end so we set this so that we know we have to add
              // one each time we add a path.
              //
              bAddBackslash = TRUE;
          }
      }
  
      // Loop through all the semicolon separated paths in the
      // buffer passed to use.
      //
      do
      {
          // Find the beginning of the path past all
          // the semicolons.
          //
          while ( _T(';') == *lpszBegin )
          {
              lpszBegin++;
          }
  
          if ( *lpszBegin )
          {
              // Find the end of the path which is the next
              // semicolon or the end of the string, whichever
              // comes first.
              //
              lpszEnd = lpszBegin;
              while ( *lpszEnd && ( _T(';') != *lpszEnd ) )
              {
                  lpszEnd++;
              }
  
              // See if our new path has a backslash at the
              // beginning of it.
              //
              dwBackslash = 0;
              if ( _T('\\') == *lpszBegin )
              {
                  // If it does and we don't want to add one,
                  // then advance the pointer past it.
                  //
                  if ( !bAddBackslash )
                  {
                      lpszBegin++;
                  }
              }
              else if ( bAddBackslash )
              {
                  // Set this so we know to add the backslash and
                  // allocate the extra space for it.
                  //
                  dwBackslash = 1;
              }
  
              // Figure out the size we need for the path we are going
              // to create.  It is the length of the new string, plus
              // the root if one was passed in, plus 1 for the backslash
              // if we need to add one, plus 2 extra (one for the null
              // terminator and one just to be safe).
              //
              dwSize = ((int) (lpszEnd - lpszBegin)) + dwBackslash + 2;
              if ( lpszRoot )
              {
                  dwSize += lstrlen(lpszRoot);
              }
  
              // Now allocate our path buffer.
              //
              if ( lpszPath = (LPTSTR) MALLOC(dwSize * sizeof(TCHAR)) )
              {
                  // Reset this so if anything doesn't work we know to
                  // free our allocated memory.
                  //
                  bAdded = FALSE;
  
                  // Copy the path into our buffer.
                  //
                  lpszCat = lpszPath;
                  if ( lpszRoot )
                  {
                      lstrcpy(lpszCat, lpszRoot);
                      lpszCat += lstrlen(lpszCat);
                  }
                  if ( dwBackslash )
                  {
                      *lpszCat++ = _T('\\');
                  }
                  lstrcpyn(lpszCat, lpszBegin, (int) (lpszEnd - lpszBegin) + 1);
  
                  if ( lpszExpanded = AllocateExpand(lpszPath) )
                  {
                      // Add it to our lists.
                      //
                      bAdded = AddPathToList(lpszExpanded, lpszPath, lplpSorted, lplpUnsorted);
  
                      // If this is a recursive add, we try to enumerate all the
                      // sub directories and add them as well.
                      //
                      if ( ( bRecursive ) &&
                           ( DirectoryExists(lpszExpanded) ) &&
                           ( SetCurrentDirectory(lpszExpanded) ) )
                      {
                          EnumeratePath(lpszPath, lplpSorted, lplpUnsorted);
                      }
  
                      // If it wasn't added to the list, then free the memory.
                      //
                      if ( !bAdded )
                      {
                          FREE(lpszExpanded);
                      }
                  }
  
                  // If it wasn't added to the list, then free the memory.
                  //
                  if ( !bAdded )
                  {
                      FREE(lpszPath);
                  }
              }
  
              // Reset the beginning to the next string.
              //
              lpszBegin = lpszEnd;
          }
      }
      while ( *lpszBegin );
  
      return bRet;
  }
  
  VOID CleanupSourcesDir(LPTSTR lpszSourcesDir)
  {
      UINT    i = 0;
      LPTSTR  lpEnd = NULL;
  
      // If we have a valid sources
      if ( lpszSourcesDir && 
           *lpszSourcesDir &&
           DirectoryExists(lpszSourcesDir)
         )
  
      {
          lpEnd = lpszSourcesDir + lstrlen(lpszSourcesDir);
  
          for (i = 0; ( i < AS(CleanupDirs) ); i++)
          {
              AddPath(lpszSourcesDir, CleanupDirs[i]);
              DeletePath(lpszSourcesDir);
              *lpEnd = NULLCHR;
          }
  
      }
  }
  
  
  // External functions
  //
  typedef BOOL ( *OpkCheckVersion ) ( DWORD dwMajorVersion, DWORD dwQFEVersion );
  
  // 
  // Wrapper around the syssetup OPKCheckVersion() function.
  //
  BOOL OpklibCheckVersion( DWORD dwMajorVersion, DWORD dwQFEVersion )
  {
      BOOL bRet                        = TRUE;  // Allow tool to run by default, in case we can't load syssetup or find the entry point.
      HINSTANCE       hInstSysSetup    = NULL;
      OpkCheckVersion pOpkCheckVersion = NULL;
  
      hInstSysSetup = LoadLibrary( _T("syssetup.dll") );
  
      if ( hInstSysSetup )
      {
          pOpkCheckVersion = (OpkCheckVersion) GetProcAddress( hInstSysSetup, "OpkCheckVersion" );
          if ( pOpkCheckVersion )
          {
              bRet = pOpkCheckVersion( dwMajorVersion, dwQFEVersion );
          }
  
          FreeLibrary( hInstSysSetup );
      }
  
      return bRet;
  }