Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

588 lines
22 KiB

  1. //+----------------------------------------------------------------------------
  2. //
  3. // File: cmstpex.cpp
  4. //
  5. // Module: CMCFG
  6. //
  7. // Synopsis: This file is the implementation of the CMSTP Extension Proc that
  8. // resides in cmcfg32.dll. This proc is used to modify the install
  9. // behavior of cmstp.exe based profile installs.
  10. //
  11. // Copyright (c) 1996-1999 Microsoft Corporation
  12. //
  13. // Author: quintinb Created 5-1-99
  14. //
  15. // History:
  16. //+----------------------------------------------------------------------------
  17. #include "cmmaster.h"
  18. //
  19. // For ProfileNeedsMigration
  20. //
  21. #include "needsmig.cpp"
  22. //
  23. // For GetPhoneBookPath
  24. //
  25. #include "getpbk.cpp"
  26. //
  27. // For GetAllUsersCmDir
  28. //
  29. #include <shlobj.h>
  30. #include "allcmdir.cpp"
  31. //
  32. // Duplicated from processcmdln.h
  33. //
  34. #include "cmstpex.h"
  35. #include "ver_str.h"
  36. #include <shellapi.h>
  37. //+----------------------------------------------------------------------------
  38. //
  39. // Function: RenameOldCmBits
  40. //
  41. // Synopsis: This function renames all of the old CM bits so that they will not
  42. // be loaded by the system during the launch of CM after the install.
  43. // This was to prevent problems with missing entry points (either things
  44. // we had removed or added to dlls like cmutil or cmpbk32). The problem
  45. // is that cmdial32.dll is loaded explicitly from system32 by RAS (which
  46. // has a fully qualified path). However, any other dlls first check the
  47. // load directory of the exe file . . . which was cmstp.exe in the temp
  48. // dir. Thus we were getting the newest cmdial32 but older versions of
  49. // cmutil, cmpbk, etc. Thus to fix it we now rename the CM bits to .old
  50. // (cmmgr32.exe becomes cmmgr32.exe.old for instance). This forces the
  51. // loader to pick the next best place to look for dlls, the system dir.
  52. //
  53. // Arguments: LPCTSTR szTempDir -- the temp dir path where the CM bits are
  54. //
  55. // Returns: BOOL - TRUE if Successful
  56. //
  57. // History: quintinb Created 6/2/99
  58. //
  59. //+----------------------------------------------------------------------------
  60. BOOL RenameOldCmBits (LPCTSTR szTempDir)
  61. {
  62. //
  63. // Note that we don't rename cmstp.exe because it is doing the install. We have no need to
  64. // rename it because it is already executing and we are just trying to prevent old bits
  65. // from being loaded and executed.
  66. //
  67. //
  68. // Note that cmcfg32.dll will load the old cmutil.dll while the extension proc is running.
  69. // Please be careful when you are adding cmutil entry points to cmcfg32.dll.
  70. //
  71. BOOL bReturn = TRUE;
  72. if (szTempDir)
  73. {
  74. //
  75. // Sanity Check -- make sure we are not renaming the bits in system32
  76. //
  77. TCHAR szTemp[MAX_PATH+1];
  78. if (GetSystemDirectory(szTemp, MAX_PATH))
  79. {
  80. if (0 == lstrcmpi(szTemp, szTempDir))
  81. {
  82. return FALSE;
  83. }
  84. }
  85. TCHAR szSrc[MAX_PATH + 1];
  86. TCHAR szDest[MAX_PATH + 1];
  87. LPCTSTR ArrayOfCmFiles[] =
  88. {
  89. TEXT("cmmgr32.exe"),
  90. TEXT("cmpbk32.dll"),
  91. TEXT("cmdial32.dll"),
  92. TEXT("cmdl32.exe"),
  93. TEXT("cnetcfg.dll"),
  94. TEXT("cmmon32.exe"),
  95. TEXT("cmutil.dll"),
  96. TEXT("instcm.inf"),
  97. TEXT("cmcfg32.dll"),
  98. TEXT("cnet16.dll"),
  99. TEXT("ccfg95.dll"),
  100. TEXT("cmutoa.dll"), // this probably won't ever exist in an older profile but delete anyway for interim build reasons
  101. TEXT("ccfgnt.dll")
  102. };
  103. const DWORD c_dwNumFiles = (sizeof(ArrayOfCmFiles)/sizeof(LPCTSTR));
  104. DWORD dwGreatestNumberOfChars = lstrlen(szTempDir) + 17; // 8.3 plus one for the NULL and one to count the dot and 4 for .old
  105. if (MAX_PATH > dwGreatestNumberOfChars)
  106. {
  107. for (int i = 0; i < c_dwNumFiles; i++)
  108. {
  109. wsprintf(szSrc, TEXT("%s\\%s"), szTempDir, ArrayOfCmFiles[i]);
  110. wsprintf(szDest, TEXT("%s\\%s.old"), szTempDir, ArrayOfCmFiles[i]);
  111. if (!MoveFile(szSrc, szDest))
  112. {
  113. DWORD dwError = GetLastError();
  114. //
  115. // Don't report an error because a file doesn't exist.
  116. //
  117. if (ERROR_FILE_NOT_FOUND != dwError)
  118. {
  119. bReturn = FALSE;
  120. }
  121. }
  122. }
  123. }
  124. }
  125. else
  126. {
  127. bReturn = FALSE;
  128. }
  129. return bReturn;
  130. }
  131. //+----------------------------------------------------------------------------
  132. //
  133. // Function: IsIeak5Cm
  134. //
  135. // Synopsis: This function compares the given version and build numbers against
  136. // known constants to figure out if this is an IEAK5 profile or not.
  137. //
  138. // Arguments: DWORD dwMajorAndMinorVersion -- a DWORD containing the Major Version number
  139. // in the HIWORD and the Minor Version number
  140. // in the LOWORD.
  141. // DWORD dwBuildAndQfeNumber -- a DWORD containing the Build number in the
  142. // HIWORD and the QFE number in the LOWORD.
  143. //
  144. // Returns: BOOL -- TRUE if the version numbers passed in correspond to an IEAK5 profile
  145. //
  146. // History: quintinb Created 8/2/99
  147. //
  148. //+----------------------------------------------------------------------------
  149. BOOL IsIeak5Cm(DWORD dwMajorAndMinorVersion, DWORD dwBuildAndQfeNumber)
  150. {
  151. BOOL bReturn = FALSE;
  152. const int c_Ieak5CmBuild = 1976;
  153. const int c_Ieak5CmMajorVer = 7;
  154. const int c_Ieak5CmMinorVer = 0;
  155. const DWORD c_dwIeak5Version = (c_Ieak5CmMajorVer << c_iShiftAmount) + c_Ieak5CmMinorVer;
  156. if ((c_dwIeak5Version == dwMajorAndMinorVersion) &&
  157. (c_Ieak5CmBuild == HIWORD(dwBuildAndQfeNumber)))
  158. {
  159. bReturn = TRUE;
  160. }
  161. return bReturn;
  162. }
  163. //
  164. // RasTypeDefs
  165. //
  166. typedef DWORD (WINAPI *pfnRasSetEntryPropertiesSpec)(LPCTSTR, LPCTSTR, LPRASENTRY, DWORD, LPBYTE, DWORD);
  167. //+----------------------------------------------------------------------------
  168. //
  169. // Function: EnumerateAndPreMigrateAllUserProfiles
  170. //
  171. // Synopsis: This function is called through the cmstp.exe extension proc. It
  172. // is used to pre-migrate 1.0 profiles. Any profile that needs migration
  173. // and hasn't been migrated yet (when the extension proc is called on an
  174. // install from an older profile), the connectoid is cleared so that the
  175. // CustomDialDll part of the connectoid entry is blanked out. This
  176. // prevents RasDeleteEntry being called on the connectoid by older versions
  177. // of cmstp.exe that don't know to clear the entry before calling it.
  178. // Otherwise, the RasCustomDeleteEntryNotify function is called and the whole
  179. // profile is deleted. This will only happen on 1.0 profiles that have
  180. // been dialed with but not migrated/upgraded. Please see NTRAID 379667
  181. // for further details.
  182. //
  183. // Arguments: BOOL bIeak5Profile -- If the calling profile is an IEAK5 CM profile or not
  184. //
  185. // Returns: TRUE if successful
  186. //
  187. // History: quintinb Created 8/2/99
  188. //
  189. //+----------------------------------------------------------------------------
  190. BOOL EnumerateAndPreMigrateAllUserProfiles(BOOL bIeak5Profile)
  191. {
  192. DWORD dwValueSize;
  193. HKEY hKey;
  194. DWORD dwType;
  195. DWORD dwDataSize;
  196. TCHAR szCurrentValue[MAX_PATH+1];
  197. TCHAR szCurrentData[MAX_PATH+1];
  198. //
  199. // Load RasApi32.dll and get RasSetEntryProperties from it.
  200. //
  201. pfnRasSetEntryPropertiesSpec pfnSetEntryProperties = NULL;
  202. HMODULE hRasApi32 = LoadLibrary(TEXT("RASAPI32.DLL"));
  203. if (hRasApi32)
  204. {
  205. pfnSetEntryProperties = (pfnRasSetEntryPropertiesSpec)GetProcAddress(hRasApi32,
  206. "RasSetEntryPropertiesA");
  207. if (NULL == pfnSetEntryProperties)
  208. {
  209. CMTRACE(TEXT("EnumerateAndPreMigrateAllUserProfiles -- Couldn't get RasSetEntryProperties."));
  210. return FALSE;
  211. }
  212. }
  213. else
  214. {
  215. CMTRACE(TEXT("EnumerateAndPreMigrateAllUserProfiles -- Couldn't load rasapi32.dll."));
  216. return FALSE;
  217. }
  218. //
  219. // Get the all user CM and all user PBK directories
  220. //
  221. TCHAR szCmAllUsersDir[MAX_PATH+1] = {0};
  222. LPTSTR pszPhonebook = NULL;
  223. if (GetAllUsersCmDir(szCmAllUsersDir, g_hInst))
  224. {
  225. //
  226. // TRUE is for an All-User profile
  227. //
  228. if (!GetPhoneBookPath(szCmAllUsersDir, &pszPhonebook, TRUE))
  229. {
  230. CMTRACE(TEXT("EnumerateAndPreMigrateAllUserProfiles -- GetPhoneBookPath Failed, returning."));
  231. return FALSE;
  232. }
  233. }
  234. else
  235. {
  236. CMTRACE(TEXT("EnumerateAndPreMigrateAllUserProfiles -- GetAllUsersCmDir Failed, returning."));
  237. return FALSE;
  238. }
  239. //
  240. // If its and IEAK5 profile then we need to get the System Directory
  241. //
  242. TCHAR szSysDir[MAX_PATH+1];
  243. if (bIeak5Profile)
  244. {
  245. if (0 == GetSystemDirectory(szSysDir, MAX_PATH))
  246. {
  247. CMTRACE(TEXT("EnumerateAndPreMigrateAllUserProfiles -- GetSystemDirectory Failed, returning."));
  248. return FALSE;
  249. }
  250. }
  251. //
  252. // Now enumerate all of the All User Profiles and see if they need any
  253. // Pre-Migration.
  254. //
  255. if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_pszRegCmMappings, 0, KEY_READ, &hKey))
  256. {
  257. DWORD dwIndex = 0;
  258. dwValueSize = MAX_PATH;
  259. dwDataSize = MAX_PATH;
  260. while (ERROR_SUCCESS == RegEnumValue(hKey, dwIndex, szCurrentValue, &dwValueSize, NULL, &dwType,
  261. (LPBYTE)szCurrentData, &dwDataSize))
  262. {
  263. if (REG_SZ == dwType)
  264. {
  265. MYDBGASSERT(0 != szCurrentValue[0]);
  266. MYDBGASSERT(0 != szCurrentData[0]);
  267. if (ProfileNeedsMigration(szCurrentValue, szCurrentData))
  268. {
  269. //
  270. // Use GetPhoneBookPath to get the path to the phonebook.
  271. //
  272. TCHAR szCmAllUsersDir[MAX_PATH+1] = {0};
  273. //
  274. // Use RasSetEntryProperties to clear the connectoid
  275. //
  276. RASENTRY_V500 RasEntryV5 = {0};
  277. RasEntryV5.dwSize = sizeof(RASENTRY_V500);
  278. RasEntryV5.dwType = RASET_Internet;
  279. if (bIeak5Profile)
  280. {
  281. //
  282. // Since IEAK5 didn't migrate 1.0 connectoids
  283. // properly (it writes them in %windir%\system32\pbk\rasphone.pbk
  284. // instead of under the all users profile as appropriate),
  285. // we need to set the szCustomDialDll instead of clearing it.
  286. //
  287. wsprintf(RasEntryV5.szCustomDialDll, TEXT("%s\\cmdial32.dll"), szSysDir);
  288. }
  289. // else zero the szCustomDialDll part of the entry
  290. // RasEntryV5.szCustomDialDll[0] = TEXT('\0'); -- already zero-ed
  291. DWORD dwRet = ((pfnSetEntryProperties)(pszPhonebook, szCurrentValue,
  292. (RASENTRY*)&RasEntryV5,
  293. RasEntryV5.dwSize, NULL, 0));
  294. if (ERROR_SUCCESS != dwRet)
  295. {
  296. CMTRACE3(TEXT("EnumerateAndPreMigrateAllUserProfiles -- RasSetEntryProperties failed on entry %s in %s, dwRet = %u"), szCurrentValue, MYDBGSTR(pszPhonebook), dwRet);
  297. }
  298. }
  299. }
  300. dwValueSize = MAX_PATH;
  301. dwDataSize = MAX_PATH;
  302. dwIndex++;
  303. }
  304. MYVERIFY(ERROR_SUCCESS == RegCloseKey(hKey));
  305. }
  306. else
  307. {
  308. CMTRACE(TEXT("EnumerateAndPreMigrateAllUserProfiles -- No CM mappings key to migrate."));
  309. }
  310. CmFree(pszPhonebook);
  311. return TRUE;
  312. }
  313. //+----------------------------------------------------------------------------
  314. //
  315. // Function: RunningUnderWow64
  316. //
  317. // Synopsis: This function is used to tell if a 32-bit process is running under
  318. // Wow64 on an ia64 machine. Note that if we are compiled 64-bit this
  319. // is always false. We make the determination by trying to call
  320. // GetSystemWow64Directory. If this function doesn't exist or returns
  321. // ERROR_CALL_NOT_IMPLEMENTED we know we are running on 32-bit. If the
  322. // function returns successfully we know we are running under wow64.
  323. //
  324. // Arguments: None
  325. //
  326. // Returns: BOOL - whether we are executing under wow64 or not
  327. //
  328. // History: quintinb Created 08/18/00
  329. //
  330. //+----------------------------------------------------------------------------
  331. BOOL RunningUnderWow64 ()
  332. {
  333. #ifdef _WIN64
  334. return FALSE;
  335. #else
  336. BOOL bReturn = FALSE;
  337. //
  338. // First get a module handle for kernel32.dll. Note it isn't necessary
  339. // to free this handle as GetModuleHandle doesn't change the ref count.
  340. //
  341. HMODULE hKernel32 = GetModuleHandle(TEXT("kernel32.dll"));
  342. if (hKernel32)
  343. {
  344. //
  345. // Next get the function pointer for GetSystemWow64Directory
  346. //
  347. typedef UINT (WINAPI *pfnGetSystemWow64DirectorySpec)(LPTSTR, UINT);
  348. #ifdef UNICODE
  349. const CHAR* const c_pszGetSystemWow64FuncName = "GetSystemWow64DirectoryW";
  350. #else
  351. const CHAR* const c_pszGetSystemWow64FuncName = "GetSystemWow64DirectoryA";
  352. #endif
  353. pfnGetSystemWow64DirectorySpec pfnGetSystemWow64Directory = (pfnGetSystemWow64DirectorySpec)GetProcAddress(hKernel32, c_pszGetSystemWow64FuncName);
  354. if (pfnGetSystemWow64Directory)
  355. {
  356. TCHAR szSysWow64Path[MAX_PATH+1] = TEXT("");
  357. //
  358. // GetSystemWow64Directory returns the number of chars copied to the buffer.
  359. // If we get zero back, then we need to check the last error code to see what the
  360. // reason for failure was. If it was call not implemented then we know we are
  361. // running on native x86.
  362. //
  363. UINT uReturn = pfnGetSystemWow64Directory(szSysWow64Path, MAX_PATH);
  364. DWORD dwError = GetLastError();
  365. CMTRACE2(TEXT("RunningUnderWow64 -- GetSystemWow64Directory returned \"%s\" and %d"), szSysWow64Path, uReturn);
  366. if (uReturn)
  367. {
  368. bReturn = TRUE;
  369. }
  370. else
  371. {
  372. CMTRACE1(TEXT("RunningUnderWow64 -- GetSystemWow64Directory returned zero, checking GLE=%d"), dwError);
  373. if (ERROR_CALL_NOT_IMPLEMENTED == dwError)
  374. {
  375. bReturn = FALSE;
  376. }
  377. else
  378. {
  379. //
  380. // We got an error, the return value is indeterminant. Let's take a backup method
  381. // of looking for %windir%\syswow64 and see if we can find one.
  382. //
  383. if (GetWindowsDirectory (szSysWow64Path, MAX_PATH))
  384. {
  385. lstrcat(szSysWow64Path, TEXT("\\syswow64"));
  386. HANDLE hDir = CreateFile(szSysWow64Path, GENERIC_READ,
  387. FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
  388. OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
  389. CMTRACE2(TEXT("RunningUnderWow64 -- Fall back algorithm. Does \"%s\" exist? %d"), szSysWow64Path, (INVALID_HANDLE_VALUE != hDir));
  390. if (INVALID_HANDLE_VALUE != hDir)
  391. {
  392. bReturn = TRUE;
  393. CloseHandle(hDir);
  394. }
  395. }
  396. }
  397. }
  398. }
  399. }
  400. return bReturn;
  401. #endif
  402. }
  403. //+----------------------------------------------------------------------------
  404. //
  405. // Function: CmstpExtensionProc
  406. //
  407. // Synopsis: This function is called by cmstp.exe right after it processes the command
  408. // line and again after it completes its action. It is most useful for modifying
  409. // the install behavior of profiles. Since the cmstp.exe that shipped with the profile,
  410. // not the current version of cmstp.exe is used for the install, this proc
  411. // allows us to change the install flags, change the inf path, and tell cmstp
  412. // to continue or silently fail the install. This version of the proc looks for
  413. // old versions of cmstp.exe and then copies the installation files to a temporary
  414. // directory and then launches the system's version of cmstp.exe with the new
  415. // install directory and parameters (we add the /c switch so that cmstp.exe knows to
  416. // cleanup after itself and to wait on the mutex).
  417. //
  418. // Arguments: LPDWORD pdwFlags - command line flags parsed by cmstp.exe
  419. // LPTSTR pszInfFile - Path to the original INF file
  420. // HRESULT hrRet - Return value of the action, only really used for POST
  421. // EXTENSIONDLLPROCTIMES PreOrPost - PRE or POST, tells when we are being called.
  422. //
  423. // Returns: BOOL - Whether Cmstp.exe should continue the existing install or not.
  424. //
  425. // History: quintinb Created 6/2/99
  426. //
  427. //+----------------------------------------------------------------------------
  428. BOOL CmstpExtensionProc(LPDWORD pdwFlags, LPTSTR pszInfFile, HRESULT hrRet, EXTENSIONDLLPROCTIMES PreOrPost)
  429. {
  430. //
  431. // We don't want 32-bit profiles installing on 64-bit. Note that the 32-bit version of cmcfg32.dll will be
  432. // in the syswow64 dir on a 64-bit machine. Thus the code below will kick in when a 32-bit version of this
  433. // function is used on a 64-bit machine. We also don't want 32-bit profiles trying to do migration, uninstalling,
  434. // etc.
  435. //
  436. if (RunningUnderWow64())
  437. {
  438. //
  439. // If this is an install, show an error message about not being able to install 32-bit profiles
  440. // on 64-bit.
  441. //
  442. if (0 == ((*pdwFlags) & 0xFF))
  443. {
  444. //
  445. // Get the long service name from the Inf
  446. //
  447. TCHAR szServiceName[MAX_PATH+1] = TEXT("");
  448. TCHAR szMsg[MAX_PATH+1] = TEXT("");
  449. MYVERIFY(0 != GetPrivateProfileString(c_pszInfSectionStrings, c_pszCmEntryServiceName, TEXT(""), szServiceName, CELEMS(szServiceName), pszInfFile));
  450. MYVERIFY(0 != LoadString(g_hInst, IDS_NO_I386_ON_IA64, szMsg, MAX_PATH));
  451. MYVERIFY(IDOK == MessageBox(NULL, szMsg, szServiceName, MB_OK));
  452. }
  453. //
  454. // Fail whatever operation we were called to do
  455. //
  456. return FALSE;
  457. }
  458. //
  459. // If the first two Hex digits in the flags value are Zero, then we have an install.
  460. // Otherwise we have some other command that we wish to ignore. We also are only
  461. // interested in processing PRE install calls.
  462. //
  463. if ((0 == ((*pdwFlags) & 0xFF)) && (PRE == PreOrPost))
  464. {
  465. CMTRACE(TEXT("CmstpExtensionProc -- Entering the cmstpex processing loop."));
  466. //
  467. // We only wish to re-launch the install with the current cmstp.exe if we are dealing with an
  468. // older install. Thus check the version stamp in the inf file. We will re-launch any
  469. // profiles with the version number less than the current version number of cmdial32.dll.
  470. //
  471. CmVersion CmVer;
  472. DWORD dwProfileVersionNumber = (DWORD)GetPrivateProfileInt(c_pszSectionCmDial32, c_pszVersion, 0,
  473. pszInfFile);
  474. DWORD dwProfileBuildNumber = (DWORD)GetPrivateProfileInt(c_pszSectionCmDial32, c_pszVerBuild, 0,
  475. pszInfFile);
  476. if ((CmVer.GetVersionNumber() > dwProfileVersionNumber) ||
  477. ((CmVer.GetVersionNumber() == dwProfileVersionNumber) &&
  478. (CmVer.GetBuildAndQfeNumber() > dwProfileBuildNumber)))
  479. {
  480. //
  481. // Then we need to delete the CM bits included with the profile because
  482. // otherwise we will get install errors due to the fact that the profile
  483. // will be launched with the cmdial32.dll from system32 (the path is
  484. // explicitly specified in the connectoid for the custom dial dll), but
  485. // the load path used by the system is the directory from which the exe
  486. // module loaded (the temp dir in this case). Thus we will get a mixed
  487. // set of bits (cmdial32.dll from system32 and cmutil.dll, cmpbk32.dll, etc.
  488. // from the cab).
  489. //
  490. TCHAR szTempDir[MAX_PATH+1];
  491. lstrcpy (szTempDir, pszInfFile);
  492. LPTSTR pszSlash = CmStrrchr(szTempDir, TEXT('\\'));
  493. if (pszSlash)
  494. {
  495. //
  496. // Then we found a last slash, Zero Terminate.
  497. //
  498. *pszSlash = TEXT('\0');
  499. //
  500. // Now we have the old temp dir path. Lets delete the old
  501. // CM bits
  502. //
  503. MYVERIFY(0 != RenameOldCmBits (szTempDir));
  504. }
  505. //
  506. // We also need to make sure that there aren't any 1.0 profiles that have a
  507. // 1.2 connectoid but only have a 1.0 registry format (thus they still have
  508. // a 1.0 desktop GUID interface). The problem here is that installation will try
  509. // to run profile migration on these connectoids. Older versions of cmstp.exe
  510. // would delete the existing connectoids and make new ones during profile migration.
  511. // The problem is that on NT5 we now respond to the RasCustomDeleteEntryNotify call, and
  512. // thus will uninstall profiles that have RasDeleteEntry called on their main connectoid.
  513. // To prevent this, we must pre-migrate older profiles and delete the new connectoid
  514. // properly.
  515. //
  516. EnumerateAndPreMigrateAllUserProfiles(IsIeak5Cm(dwProfileVersionNumber, dwProfileBuildNumber));
  517. }
  518. }
  519. return TRUE; // always return TRUE so that cmstp.exe continues. Only change this if you want cmstp.exe
  520. // to fail certain actions.
  521. }